from __future__ import annotations

import base64
from dataclasses import dataclass
from typing import Any

from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA

from ..backend_client import BackendClient
from ..config import get_default_base_url, get_timeout_seconds, normalize_base_url
from ..errors import QingflowApiError


@dataclass(slots=True)
class QingflowNativeLoginResult:
    token: str
    user_info: dict[str, Any]
    login_token: str | None = None
    flow: str = "qingflow_password"


class QingflowNativeLoginHelper:
    def __init__(self, *, backend: BackendClient | None = None) -> None:
        self._owns_backend = backend is None
        self._backend = backend or BackendClient(timeout=get_timeout_seconds())

    def close(self) -> None:
        if self._owns_backend:
            self._backend.close()

    def login_with_password(
        self,
        *,
        base_url: str | None,
        email: str,
        password: str,
    ) -> QingflowNativeLoginResult:
        normalized_base_url = normalize_base_url(base_url) or get_default_base_url()
        normalized_email = str(email or "").strip()
        normalized_password = str(password or "")
        if not normalized_base_url:
            raise QingflowApiError.config_error("base_url is required or configure default_base_url")
        if not normalized_email:
            raise QingflowApiError.config_error("email is required for Qingflow account login")
        if not normalized_password:
            raise QingflowApiError.config_error("password is required for Qingflow account login")

        pubkey_payload = self._backend.public_request("GET", normalized_base_url, "/user/pubkey", qf_version=None)
        pubkey = self._extract_pubkey(pubkey_payload)
        encrypted_password = _encrypt_password(normalized_password, pubkey)
        login_payload = self._backend.public_request(
            "POST",
            normalized_base_url,
            "/user/login",
            json_body={"email": normalized_email, "password": encrypted_password},
            qf_version=None,
        )
        if not isinstance(login_payload, dict):
            raise QingflowApiError(category="auth", message="Qingflow login did not return a valid response")

        token = str(login_payload.get("token") or "").strip()
        login_token = str(login_payload.get("loginToken") or "").strip() or None
        if not token:
            if login_token:
                raise QingflowApiError(
                    category="auth",
                    message=(
                        "Qingflow account login requires additional security verification. "
                        "CLI password login currently does not complete the loginToken verification step."
                    ),
                    details={"login_token_present": True},
                )
            raise QingflowApiError(
                category="auth",
                message="Qingflow login succeeded but did not return a token",
            )

        user_info = login_payload.get("userInfo")
        if not isinstance(user_info, dict):
            user_info = {}
        return QingflowNativeLoginResult(
            token=token,
            login_token=login_token,
            user_info=user_info,
        )

    def _extract_pubkey(self, payload: Any) -> str:
        if not isinstance(payload, dict):
            raise QingflowApiError(category="auth", message="Qingflow pubkey response is invalid")
        pubkey = str(payload.get("pubkey") or "").strip()
        if not pubkey:
            raise QingflowApiError(category="auth", message="Qingflow pubkey response did not include pubkey")
        return pubkey


def login_with_qingflow_password(
    *,
    base_url: str | None,
    email: str,
    password: str,
) -> QingflowNativeLoginResult:
    helper = QingflowNativeLoginHelper()
    try:
        return helper.login_with_password(base_url=base_url, email=email, password=password)
    finally:
        helper.close()


def _encrypt_password(password: str, pubkey: str) -> str:
    public_key = RSA.import_key(
        "-----BEGIN PUBLIC KEY-----\n" + pubkey.strip() + "\n-----END PUBLIC KEY-----\n"
    )
    cipher = PKCS1_v1_5.new(public_key)
    encrypted = cipher.encrypt(password.encode("utf-8"))
    return base64.b64encode(encrypted).decode("ascii")
