from __future__ import annotations

import asyncio
import hashlib
import hmac
import json
import os
import secrets
import sys
from dataclasses import dataclass
from datetime import datetime
from typing import Any

from core.utils import get_app_data_path

AUTH_FILE = get_app_data_path(os.path.join("auth", "accounts.json"))
SETTINGS_FILE = get_app_data_path("settings.json")
MIN_PASSWORD_LENGTH = 6


@dataclass
class AuthResult:
    ok: bool
    message: str
    account: dict[str, Any] | None = None
    sync_ok: bool = False
    sync_message: str = ""


def normalize_tax_code(tax_code: str | None) -> str:
    return "".join(ch for ch in (tax_code or "").strip() if ch.isdigit())


def _now_iso() -> str:
    return datetime.now().isoformat(timespec="seconds")


def _default_store() -> dict[str, Any]:
    return {
        "version": 1,
        "accounts": {},
        "session": {
            "last_tax_id": "",
            "remember": False,
            "logged_in_at": "",
        },
    }


def _ensure_parent_dir(path: str) -> None:
    os.makedirs(os.path.dirname(path), exist_ok=True)


def _load_store() -> dict[str, Any]:
    if not os.path.exists(AUTH_FILE):
        return _default_store()
    try:
        with open(AUTH_FILE, "r", encoding="utf-8") as file_obj:
            data = json.load(file_obj)
        if not isinstance(data, dict):
            return _default_store()
        data.setdefault("accounts", {})
        data.setdefault("session", {})
        return data
    except Exception:
        return _default_store()


def _save_store(store: dict[str, Any]) -> None:
    _ensure_parent_dir(AUTH_FILE)
    with open(AUTH_FILE, "w", encoding="utf-8") as file_obj:
        json.dump(store, file_obj, indent=2, ensure_ascii=False)


def _load_settings() -> dict[str, Any]:
    if not os.path.exists(SETTINGS_FILE):
        return {}
    try:
        with open(SETTINGS_FILE, "r", encoding="utf-8") as file_obj:
            data = json.load(file_obj)
        return data if isinstance(data, dict) else {}
    except Exception:
        return {}


def _save_settings(data: dict[str, Any]) -> None:
    _ensure_parent_dir(SETTINGS_FILE)
    with open(SETTINGS_FILE, "w", encoding="utf-8") as file_obj:
        json.dump(data, file_obj, indent=4, ensure_ascii=False)


def _make_password_digest(password: str, salt_hex: str) -> str:
    digest = hashlib.pbkdf2_hmac(
        "sha256",
        (password or "").encode("utf-8"),
        bytes.fromhex(salt_hex),
        200_000,
    )
    return digest.hex()


def _build_password_bundle(password: str) -> tuple[str, str]:
    salt_hex = secrets.token_hex(16)
    return salt_hex, _make_password_digest(password, salt_hex)


def _verify_password(password: str, salt_hex: str, digest_hex: str) -> bool:
    expected = _make_password_digest(password, salt_hex)
    return hmac.compare_digest(expected, digest_hex)


def _public_account(account: dict[str, Any]) -> dict[str, Any]:
    return {
        "tax_id": account.get("tax_id", ""),
        "username": account.get("username", ""),
        "company_info": dict(account.get("company_info", {}) or {}),
        "created_at": account.get("created_at", ""),
        "updated_at": account.get("updated_at", ""),
        "last_login_at": account.get("last_login_at", ""),
        "sync_ok": bool(account.get("sync_ok", False)),
        "sync_message": account.get("sync_message", ""),
    }


def get_local_company_info() -> dict[str, Any]:
    data = _load_settings()
    company = data.get("company", {})
    return dict(company) if isinstance(company, dict) else {}


def get_last_tax_code() -> str:
    store = _load_store()
    session = store.get("session", {})
    last_tax_id = normalize_tax_code(session.get("last_tax_id", ""))
    if last_tax_id:
        return last_tax_id
    company = get_local_company_info()
    return normalize_tax_code(company.get("tax_id", ""))


def has_any_account() -> bool:
    store = _load_store()
    return bool(store.get("accounts", {}))


def _persist_company_settings(company_info: dict[str, Any]) -> None:
    if not isinstance(company_info, dict) or not company_info:
        return
    current = _load_settings()
    merged_company = dict(current.get("company", {}) or {})
    merged_company.update(company_info)
    current["company"] = merged_company
    current.setdefault("system", {})
    _save_settings(current)


def _import_select_signer():
    try:
        from win_signer import select_signer
        return select_signer
    except ImportError:
        root_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
        sign_dir = os.path.join(root_dir, "sign")
        if os.path.isdir(sign_dir) and sign_dir not in sys.path:
            sys.path.insert(0, sign_dir)
        from win_signer import select_signer
        return select_signer


def _tax_codes_match(left: str | None, right: str | None) -> bool:
    left_code = normalize_tax_code(left)
    right_code = normalize_tax_code(right)
    if not left_code or not right_code:
        return False
    if left_code == right_code:
        return True
    return left_code[:10] == right_code[:10]


def _run_token_signature(signer, challenge: bytes) -> bytes:
    async def _sign() -> bytes:
        return await signer.async_sign_raw(challenge, "sha256")

    try:
        return asyncio.run(_sign())
    except RuntimeError:
        event_loop = asyncio.new_event_loop()
        try:
            return event_loop.run_until_complete(_sign())
        finally:
            event_loop.close()


def _verify_usb_token_for_tax_code(tax_id: str) -> tuple[bool, str, dict[str, Any]]:
    signer = None
    try:
        select_signer = _import_select_signer()
        signer = select_signer()
        if not signer:
            return False, "Bạn đã hủy chọn chứng thư số.", {}

        token_tax_id = normalize_tax_code(getattr(signer, "mst", ""))
        subject_dn = str(getattr(signer, "subject_dn", "") or "")
        common_name = str(getattr(signer, "common_name", "") or "")

        if not token_tax_id:
            return False, "Chứng thư số chưa có MST để xác minh tài khoản.", {}
        if not _tax_codes_match(tax_id, token_tax_id):
            return False, f"MST trên USB token ({token_tax_id}) không khớp MST tài khoản ({tax_id}).", {}

        challenge = f"INI-ACCOUNT-RESET|{tax_id}|{_now_iso()}|{secrets.token_hex(16)}".encode("utf-8")
        signature = _run_token_signature(signer, challenge)
        if not signature:
            return False, "USB token không tạo được chữ ký xác thực.", {}

        return True, "USB token đã xác thực thành công.", {
            "token_tax_id": token_tax_id,
            "subject_dn": subject_dn,
            "common_name": common_name,
        }
    except ImportError as exc:
        return False, f"Không nạp được module USB token: {exc}", {}
    except Exception as exc:
        return False, f"Không xác thực được USB token: {exc}", {}
    finally:
        if signer is not None:
            try:
                del signer
            except Exception:
                pass


def register_account(company_info: dict[str, Any], password: str, confirm_password: str) -> AuthResult:
    tax_id = normalize_tax_code((company_info or {}).get("tax_id"))
    legal_name = str((company_info or {}).get("legal_name", "")).strip()

    if not tax_id:
        return AuthResult(False, "MST không hợp lệ.")
    if not legal_name:
        return AuthResult(False, "Tên pháp lý là bắt buộc.")
    if len((password or "").strip()) < MIN_PASSWORD_LENGTH:
        return AuthResult(False, f"Mật khẩu phải có ít nhất {MIN_PASSWORD_LENGTH} ký tự.")
    if password != confirm_password:
        return AuthResult(False, "Mật khẩu xác nhận chưa khớp.")

    store = _load_store()
    accounts = store.setdefault("accounts", {})
    if tax_id in accounts:
        return AuthResult(False, "MST này đã có tài khoản. Bạn hãy đăng nhập.")

    normalized_company = dict(company_info or {})
    normalized_company["tax_id"] = tax_id

    salt_hex, digest_hex = _build_password_bundle(password)
    timestamp = _now_iso()
    account = {
        "tax_id": tax_id,
        "username": tax_id,
        "password_salt": salt_hex,
        "password_hash": digest_hex,
        "company_info": normalized_company,
        "created_at": timestamp,
        "updated_at": timestamp,
        "last_login_at": "",
        "sync_ok": False,
        "sync_message": "",
        "last_password_reset_at": "",
        "last_password_reset_method": "",
        "last_token_subject": "",
    }
    accounts[tax_id] = account
    _save_store(store)
    _persist_company_settings(normalized_company)

    return AuthResult(
        ok=True,
        message="Đăng ký tài khoản thành công.",
        account=_public_account(account),
        sync_ok=False,
        sync_message="",
    )


def login_user(tax_code: str, password: str, remember: bool = False) -> AuthResult:
    tax_id = normalize_tax_code(tax_code)
    if not tax_id:
        return AuthResult(False, "Bạn hãy nhập MST để đăng nhập.")

    store = _load_store()
    account = (store.get("accounts", {}) or {}).get(tax_id)
    if not isinstance(account, dict):
        return AuthResult(False, "MST này chưa có tài khoản. Bạn hãy đăng ký trước.")

    if not _verify_password(password, account.get("password_salt", ""), account.get("password_hash", "")):
        return AuthResult(False, "Mật khẩu chưa đúng.")

    timestamp = _now_iso()
    account["last_login_at"] = timestamp
    account["updated_at"] = timestamp
    store["session"] = {
        "last_tax_id": tax_id,
        "remember": bool(remember),
        "logged_in_at": timestamp,
    }
    _save_store(store)
    _persist_company_settings(account.get("company_info", {}))

    return AuthResult(
        ok=True,
        message="Đăng nhập thành công.",
        account=_public_account(account),
        sync_ok=bool(account.get("sync_ok", False)),
        sync_message=str(account.get("sync_message", "")),
    )


def reset_password_with_usb_token(tax_code: str, new_password: str, confirm_password: str) -> AuthResult:
    tax_id = normalize_tax_code(tax_code)
    if not tax_id:
        return AuthResult(False, "Bạn hãy nhập MST của tài khoản cần tạo mật khẩu mới.")
    if len((new_password or "").strip()) < MIN_PASSWORD_LENGTH:
        return AuthResult(False, f"Mật khẩu mới phải có ít nhất {MIN_PASSWORD_LENGTH} ký tự.")
    if new_password != confirm_password:
        return AuthResult(False, "Mật khẩu xác nhận chưa khớp.")

    store = _load_store()
    account = (store.get("accounts", {}) or {}).get(tax_id)
    if not isinstance(account, dict):
        return AuthResult(False, "MST này chưa có tài khoản để khôi phục mật khẩu.")

    verified, verify_message, token_meta = _verify_usb_token_for_tax_code(tax_id)
    if not verified:
        return AuthResult(False, verify_message)

    salt_hex, digest_hex = _build_password_bundle(new_password)
    timestamp = _now_iso()
    account["password_salt"] = salt_hex
    account["password_hash"] = digest_hex
    account["updated_at"] = timestamp
    account["last_password_reset_at"] = timestamp
    account["last_password_reset_method"] = "usb_token"
    account["last_token_subject"] = str(token_meta.get("subject_dn", ""))
    account["sync_ok"] = False
    account["sync_message"] = ""

    store["session"] = {
        "last_tax_id": tax_id,
        "remember": False,
        "logged_in_at": "",
    }
    _save_store(store)
    _persist_company_settings(account.get("company_info", {}))

    token_name = str(token_meta.get("common_name", "")).strip()
    message = "Đã tạo mật khẩu mới bằng USB token."
    if token_name:
        message = f"Đã xác thực chứng thư số của {token_name} và tạo mật khẩu mới."

    return AuthResult(
        ok=True,
        message=message,
        account=_public_account(account),
        sync_ok=False,
        sync_message=verify_message,
    )


def get_auto_login_account() -> dict[str, Any] | None:
    store = _load_store()
    session = store.get("session", {})
    if not bool(session.get("remember")):
        return None

    tax_id = normalize_tax_code(session.get("last_tax_id", ""))
    account = (store.get("accounts", {}) or {}).get(tax_id)
    if not isinstance(account, dict):
        return None

    _persist_company_settings(account.get("company_info", {}))
    return _public_account(account)


def sync_account_company_info(company_info: dict[str, Any]) -> bool:
    tax_id = normalize_tax_code((company_info or {}).get("tax_id"))
    if not tax_id:
        return False

    store = _load_store()
    account = (store.get("accounts", {}) or {}).get(tax_id)
    if not isinstance(account, dict):
        return False

    merged_company = dict(account.get("company_info", {}) or {})
    merged_company.update(company_info or {})
    merged_company["tax_id"] = tax_id

    account["company_info"] = merged_company
    account["updated_at"] = _now_iso()
    _save_store(store)
    _persist_company_settings(merged_company)
    return True