from urllib.parse import urlencode
from typing import Literal

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.config import settings
from app.core.security import get_jwt_validator
from app.repositories.user import MembershipRepository, UserRepository
from app.schemas.auth import (
    AuthConfigResponse,
    AuthSessionResponse,
    LoginUrlResponse,
    MeResponse,
    PasswordChangeRequest,
    PasswordChangeResponse,
    ProfileUpdateRequest,
    TokenExchangeRequest,
    TokenResponse,
)
from app.models.user import UserProfile
from app.services.keycloak_client import KeycloakAdminClient
from app.services.pkce_store import generate_pkce_pair, generate_state, pkce_store
from app.services.user_service import UserService
from shared.exceptions import NotFoundError, UnauthorizedError
from shared.security import CurrentUser


class AuthService:
    def __init__(self, session: AsyncSession) -> None:
        self.session = session
        self.users = UserRepository(session)
        self.memberships = MembershipRepository(session)
        self.keycloak = KeycloakAdminClient()

    def get_oidc_config(self) -> AuthConfigResponse:
        return AuthConfigResponse(
            issuer=settings.keycloak_public_realm_url,
            client_id=settings.keycloak_frontend_client_id,
            authorization_endpoint=settings.oidc_public_auth_url,
            token_endpoint=settings.oidc_token_url,
            logout_endpoint=settings.oidc_public_logout_url,
            userinfo_endpoint=settings.oidc_public_userinfo_url,
            callback_url=settings.frontend_callback_url,
        )

    def build_login_url(self) -> LoginUrlResponse:
        """آدرس ورود Keycloak را با state و PKCE challenge سمت سرور می‌سازد."""
        state = generate_state()
        verifier, challenge = generate_pkce_pair()
        pkce_store.put(state, verifier)
        params = {
            "client_id": settings.keycloak_frontend_client_id,
            "redirect_uri": settings.frontend_callback_url,
            "response_type": "code",
            "scope": "openid profile email",
            "state": state,
            "code_challenge": challenge,
            "code_challenge_method": "S256",
        }
        url = f"{settings.oidc_public_auth_url}?{urlencode(params)}"
        return LoginUrlResponse(authorization_url=url, state=state)

    async def exchange_token(self, data: TokenExchangeRequest) -> TokenResponse:
        verifier = data.code_verifier
        if data.state:
            stored = pkce_store.pop(data.state)
            if stored:
                verifier = stored
        raw = await self.keycloak.exchange_code(
            data.code, data.redirect_uri, verifier
        )
        # همگام‌سازی پروفایل کاربر پس از لاگین موفق
        user_claims = await get_jwt_validator().validate(raw["access_token"])
        await UserService(self.session).get_or_create_from_sub(
            sub=user_claims.user_id,
            email=user_claims.email,
            username=user_claims.username,
        )
        return TokenResponse(
            access_token=raw["access_token"],
            refresh_token=raw.get("refresh_token"),
            expires_in=raw.get("expires_in", 3600),
            token_type=raw.get("token_type", "Bearer"),
        )

    async def refresh_token(self, refresh_token: str) -> TokenResponse:
        raw = await self.keycloak.refresh_tokens(refresh_token)
        return TokenResponse(
            access_token=raw["access_token"],
            refresh_token=raw.get("refresh_token") or refresh_token,
            expires_in=raw.get("expires_in", 3600),
            token_type=raw.get("token_type", "Bearer"),
        )

    async def issue_session_for_profile(
        self,
        profile: UserProfile,
        *,
        message: str,
        intent: Literal["login", "register"],
        password: str | None = None,
        sync_mobile_password: bool = False,
    ) -> AuthSessionResponse:
        """صدور توکن SSO پس از تأیید OTP یا تکمیل ثبت‌نام."""
        username = profile.username or profile.mobile or ""
        raw = await self.keycloak.issue_tokens_for_user(
            keycloak_sub=profile.keycloak_sub,
            username=username,
            mobile=profile.mobile,
            password=password,
            sync_mobile_password=sync_mobile_password,
        )
        return AuthSessionResponse(
            access_token=raw["access_token"],
            refresh_token=raw.get("refresh_token"),
            expires_in=raw.get("expires_in", 3600),
            token_type=raw.get("token_type", "Bearer"),
            message=message,
            intent=intent,
            user_id=str(profile.id),
            mobile_verified=profile.mobile_verified,
        )

    async def _build_me(self, current: CurrentUser, user: UserProfile | None) -> MeResponse:
        memberships = []
        if user:
            rows = await self.memberships.list_by_user(user.id)
            memberships = [
                {"tenant_id": str(m.tenant_id), "role": m.role.value, "is_active": m.is_active}
                for m in rows
            ]
        return MeResponse(
            user_id=current.user_id,
            keycloak_sub=current.user_id,
            email=(user.email if user else None) or current.email,
            username=(user.username if user else None) or current.username,
            display_name=user.display_name if user else None,
            mobile=user.mobile if user else None,
            mobile_verified=user.mobile_verified if user else False,
            requires_mobile_verification=bool(user and not user.mobile_verified),
            roles=current.roles,
            tenant_memberships=memberships,
        )

    async def get_me(self, current: CurrentUser) -> MeResponse:
        user = await self.users.get_by_sub(current.user_id)
        return await self._build_me(current, user)

    async def update_profile(
        self, current: CurrentUser, data: ProfileUpdateRequest
    ) -> MeResponse:
        """به‌روزرسانی پروفایل کاربر جاری + همگام‌سازی با Keycloak."""
        user = await self.users.get_by_sub(current.user_id)
        if user is None:
            raise NotFoundError("پروفایل کاربر یافت نشد")

        new_email: str | None = None
        if data.email is not None:
            candidate = str(data.email)
            if candidate != user.email:
                user.email = candidate
                new_email = candidate

        if data.display_name is not None:
            user.display_name = data.display_name.strip() or None

        await self.session.flush()
        await self.keycloak.update_user(
            current.user_id,
            email=new_email,
            first_name=user.display_name if data.display_name is not None else None,
        )
        await self.session.commit()
        await self.session.refresh(user)
        return await self._build_me(current, user)

    async def change_password(
        self, current: CurrentUser, data: PasswordChangeRequest
    ) -> PasswordChangeResponse:
        """تغییر/تنظیم رمز عبور کاربر جاری در Keycloak."""
        user = await self.users.get_by_sub(current.user_id)
        if user is None:
            raise NotFoundError("پروفایل کاربر یافت نشد")

        if data.current_password:
            login_name = user.username or user.mobile or current.username or ""
            try:
                await self.keycloak.password_login(login_name, data.current_password)
            except UnauthorizedError:
                raise UnauthorizedError("رمز عبور فعلی نادرست است")

        await self.keycloak.set_user_password(current.user_id, data.new_password)
        return PasswordChangeResponse(message="رمز عبور با موفقیت به‌روزرسانی شد")
