import logging
import os
import time
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, Optional, Tuple

from core.auth import create_access_token
from core.config import settings
from core.database import db_manager
from models.auth import OIDCState, User
from sqlalchemy import delete, select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)


class AuthService:
    def __init__(self, db: AsyncSession):
        self.db = db

    async def get_or_create_user(self, platform_sub: str, email: str, name: Optional[str] = None) -> User:
        """Get existing user or create new one."""
        start_time = time.time()
        logger.debug(f"[DB_OP] Starting get_or_create_user - platform_sub: {platform_sub}")
        # Try to find existing user
        result = await self.db.execute(select(User).where(User.id == platform_sub))
        user = result.scalar_one_or_none()
        logger.debug(f"[DB_OP] User lookup completed in {time.time() - start_time:.4f}s - found: {user is not None}")

        if user:
            # Update user info if needed
            user.email = email
            user.name = name
            user.last_login = datetime.now(timezone.utc)
        else:
            # Create new user
            user = User(id=platform_sub, email=email, name=name, last_login=datetime.now(timezone.utc))
            self.db.add(user)

        start_time_commit = time.time()
        logger.debug("[DB_OP] Starting user commit/refresh")
        await self.db.commit()
        await self.db.refresh(user)
        logger.debug(f"[DB_OP] User commit/refresh completed in {time.time() - start_time_commit:.4f}s")
        return user

    async def issue_app_token(
        self,
        user: User,
    ) -> Tuple[str, datetime, Dict[str, Any]]:
        """Generate application JWT token for the authenticated user."""
        try:
            expires_minutes = int(getattr(settings, "jwt_expire_minutes", 60))
        except (TypeError, ValueError):
            logger.warning("Invalid JWT_EXPIRE_MINUTES value; fallback to 60 minutes")
            expires_minutes = 60
        expires_at = datetime.now(timezone.utc) + timedelta(minutes=expires_minutes)

        claims: Dict[str, Any] = {
            "sub": user.id,
            "email": user.email,
            "role": user.role,
        }

        if user.name:
            claims["name"] = user.name
        if user.last_login:
            claims["last_login"] = user.last_login.isoformat()
        token = create_access_token(claims, expires_minutes=expires_minutes)

        return token, expires_at, claims

    async def store_oidc_state(self, state: str, nonce: str, code_verifier: str):
        """Store OIDC state in database."""
        # Clean up expired states first
        await self.db.execute(delete(OIDCState).where(OIDCState.expires_at < datetime.now(timezone.utc)))

        expires_at = datetime.now(timezone.utc) + timedelta(minutes=10)  # 10 minute expiry

        oidc_state = OIDCState(state=state, nonce=nonce, code_verifier=code_verifier, expires_at=expires_at)

        self.db.add(oidc_state)
        await self.db.commit()

    async def get_and_delete_oidc_state(self, state: str) -> Optional[dict]:
        """Get and delete OIDC state from database."""
        # Clean up expired states first
        await self.db.execute(delete(OIDCState).where(OIDCState.expires_at < datetime.now(timezone.utc)))

        # Find and validate state
        result = await self.db.execute(select(OIDCState).where(OIDCState.state == state))
        oidc_state = result.scalar_one_or_none()

        if not oidc_state:
            return None

        # Extract data before deleting
        state_data = {"nonce": oidc_state.nonce, "code_verifier": oidc_state.code_verifier}

        # Delete the used state (one-time use)
        await self.db.delete(oidc_state)
        await self.db.commit()

        return state_data


async def initialize_admin_user():
    """Initialize admin user(s) if not exists.

    Handles two admin sources:
      1. Platform/OIDC admin: derived from ``settings.admin_user_id``.
      2. Local email/password admin: seeded from ``LOCAL_ADMIN_EMAIL`` /
         ``LOCAL_ADMIN_PASSWORD`` env vars, with sensible defaults so the
         operator always has a working admin login.
    """
    if "MGX_IGNORE_INIT_ADMIN" in os.environ:
        logger.info("Ignore initialize admin")
        return

    from services.database import initialize_database

    # Ensure database is initialized first
    await initialize_database()

    admin_user_id = getattr(settings, "admin_user_id", "")
    admin_user_email = getattr(settings, "admin_user_email", "")

    async with db_manager.async_session_maker() as db:
        # --- Platform/OIDC admin (legacy behaviour) ---
        if admin_user_id and admin_user_email:
            result = await db.execute(select(User).where(User.id == admin_user_id))
            user = result.scalar_one_or_none()

            if user:
                if user.role != "admin":
                    user.role = "admin"
                    user.email = admin_user_email
                    await db.commit()
                    logger.debug(f"Updated user {admin_user_id} to admin role")
                else:
                    logger.debug(f"Admin user {admin_user_id} already exists")
            else:
                admin_user = User(
                    id=admin_user_id,
                    email=admin_user_email,
                    role="admin",
                )
                db.add(admin_user)
                await db.commit()
                logger.debug(f"Created platform admin user: {admin_user_id}")
        else:
            logger.info("Platform admin user ID/email not configured, skipping platform admin init")

        # --- Local email/password admin ---
        local_admin_email = os.getenv("LOCAL_ADMIN_EMAIL", "admin@doiteasy.net")
        local_admin_password = os.getenv("LOCAL_ADMIN_PASSWORD", "@#$P@ssw0rd")
        local_admin_name = os.getenv("LOCAL_ADMIN_NAME", "Site Admin")
        try:
            from services.local_auth import ensure_local_admin

            await ensure_local_admin(
                db,
                email=local_admin_email,
                password=local_admin_password,
                name=local_admin_name,
            )
        except Exception:  # noqa: BLE001
            logger.exception("[local_auth] Failed to seed local admin user")
