from typing import Any
import bcrypt
from fastapi import HTTPException, status

from app.auth.repository import AuthRepository
from app.auth.schemas import (
    LoginRequest,
    LoginResponse,
    LogoutResponse,
    UserProfile,
)
from app.config import Settings
from app.security import Role, create_access_token


class AuthService:
    def __init__(self, repository: AuthRepository, settings: Settings) -> None:
        self.repository = repository
        self.settings = settings

    @staticmethod
    def verify_password(plain_password: str, hashed_password: str | None) -> bool:
        if not hashed_password:
            return False
        # Check if bcrypt hash ($2a$, $2b$, $2y$)
        if hashed_password.startswith(("$2a$", "$2b$", "$2y$")):
            try:
                pwd_bytes = hashed_password.encode("utf-8")
                # Normalize $2y$ prefix to $2b$ for bcrypt if needed
                if pwd_bytes.startswith(b"$2y$"):
                    pwd_bytes = b"$2b$" + pwd_bytes[4:]
                return bcrypt.checkpw(plain_password.encode("utf-8"), pwd_bytes)
            except Exception:
                return False
        # Legacy plain text comparison
        return plain_password == hashed_password

    @staticmethod
    def map_role(hakakses: str | None, is_sales: bool = False) -> Role:
        if is_sales:
            return "SALES"
        if not hakakses:
            return "VIEWER"
        upper = hakakses.strip().upper()
        if any(keyword in upper for keyword in ("OWNER", "DEVELOPER", "DEV")):
            return "OWNER"
        if any(keyword in upper for keyword in ("SUPERVISOR", "ADMIN")):
            return "SUPERVISOR"
        if "SALES" in upper:
            return "SALES"
        return "VIEWER"

    async def authenticate(
        self,
        request: LoginRequest,
        client_ip: str | None = None,
    ) -> LoginResponse:
        username = request.username.strip()
        user_code: str = ""
        user_name: str = ""
        hak_akses: str = ""
        kode_divisi: str | None = None
        source: str = ""
        sales_lookup_name: str | None = None

        # 1. Try finding in mspengguna
        pengguna = await self.repository.find_pengguna(username)
        if pengguna and self.verify_password(
            request.password, pengguna.get("password")
        ):
            source = "PENGGUNA"
            user_code = pengguna["kodepengguna"]
            user_name = pengguna.get("namapengguna") or user_code
            hak_akses = pengguna.get("hakakses") or "VIEWER"
            kode_divisi = pengguna.get("kodedivisi")
            sales_lookup_name = pengguna.get("namapengguna") or user_code
        else:
            # 2. Try finding in mssales
            sales = await self.repository.find_sales(username)
            if sales and self.verify_password(
                request.password, sales.get("password")
            ):
                source = "SALES"
                user_code = sales["kodesales"]
                user_name = sales.get("namasales") or user_code
                hak_akses = "SALES"
                kode_divisi = None

        if not source:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail="Username atau password salah",
                headers={"WWW-Authenticate": "Bearer"},
            )

        mapped_role = self.map_role(hak_akses, is_sales=(source == "SALES"))
        kode_sales: str | None = (
            user_code if (source == "SALES" or mapped_role == "SALES") else None
        )
        if source == "PENGGUNA" and mapped_role == "SALES":
            linked_sales = await self.repository.find_sales(
                (sales_lookup_name or user_code).strip()
            )
            if linked_sales is None:
                raise HTTPException(
                    status_code=status.HTTP_403_FORBIDDEN,
                    detail=(
                        "Akun pengguna Sales belum terhubung ke sales aktif. "
                        "Hubungkan nama pengguna ke kode atau nama sales."
                    ),
                )
            kode_sales = linked_sales["kodesales"]

        # Audit login into mslogin
        login_id: int | None = None
        try:
            login_id = await self.repository.record_login(
                kodepengguna=user_code,
                namahost=request.device_name,
                jenisaplikasi=request.app_type,
                version=request.app_version,
                ip_address=client_ip,
            )
        except Exception:
            # Audit failure should not block user authentication
            pass

        # Create JWT token
        token_payload: dict[str, Any] = {
            "sub": user_code,
            "name": user_name,
            "role": mapped_role,
            "hak_akses": hak_akses,
            "kode_divisi": kode_divisi,
            "source": source,
            "login_id": login_id,
            "kode_sales": kode_sales,
        }
        token, expires_in = create_access_token(token_payload, self.settings)

        return LoginResponse(
            access_token=token,
            token_type="bearer",
            expires_in=expires_in,
            user=UserProfile(
                user_code=user_code,
                user_name=user_name,
                role=mapped_role,
                hak_akses=hak_akses,
                kode_divisi=kode_divisi,
                source=source,  # type: ignore[arg-type]
                kode_sales=kode_sales,
            ),
            login_id=login_id,
        )

    async def logout(
        self,
        user_code: str,
        login_id: int | None,
    ) -> LogoutResponse:
        if login_id is not None:
            try:
                await self.repository.record_logout(login_id, user_code)
            except Exception:
                pass
        return LogoutResponse(status="ok", message="Berhasil logout")
