"""Provision a matching cloud tenant when a local server completes setup (same login + IDs for sync)."""
from __future__ import annotations

import json
import logging
from datetime import datetime
from decimal import Decimal
from pathlib import Path
from typing import Any, Dict, List, Optional

import httpx
from sqlalchemy.orm import Session

from app.core.config import settings
from app.models.branch import Branch
from app.models.company import Company
from app.models.licensing import LicenseKey
from app.models.user import User
from app.services.cloud_sync_service import load_node_config
from app.services.licensing_service import (
    LicensingService,
    create_mirror_provision_token,
    verify_mirror_provision_token,
    verify_setup_token,
)

logger = logging.getLogger(__name__)


class CloudMirrorProvisioner:
    """Cloud hub — create/update tenant mirror from local setup payload."""

    def __init__(self, db: Session):
        self.db = db

    def _authorize_provision(
        self,
        *,
        license_key: str,
        company_id: str,
        setup_token: Optional[str],
        mirror_provision_token: Optional[str],
        deployment: str,
    ) -> bool:
        if mirror_provision_token:
            return verify_mirror_provision_token(company_id, license_key, mirror_provision_token)
        if setup_token:
            return verify_setup_token(license_key, setup_token, deployment=deployment)
        return False

    def provision(
        self,
        *,
        license_key: str,
        setup_token: Optional[str] = None,
        mirror_provision_token: Optional[str] = None,
        deployment: str,
        company_id: str,
        company_code: str,
        company_name: str,
        admin_user_id: str,
        admin_username: str,
        admin_password_digest: str,
        hq_branch_id: str,
        hq_branch_code: str,
        legal_name: Optional[str] = None,
        admin_email: Optional[str] = None,
        admin_first_name: Optional[str] = None,
        admin_last_name: Optional[str] = None,
        phone: Optional[str] = None,
        email: Optional[str] = None,
        industry: Optional[str] = None,
        currency: str = "BWP",
        country: str = "Botswana",
        timezone: str = "Africa/Gaborone",
        accounting_codes: Optional[List[Dict[str, Any]]] = None,
        site_name: Optional[str] = None,
    ) -> Dict[str, Any]:
        dep = (deployment or "local").lower()
        if not self._authorize_provision(
            license_key=license_key,
            company_id=company_id,
            setup_token=setup_token,
            mirror_provision_token=mirror_provision_token,
            deployment=dep,
        ):
            return {"success": False, "message": "Invalid or expired mirror authorization."}

        if (settings.deployment_mode or "cloud").lower() != "cloud":
            return {"success": False, "message": "Cloud mirror provisioning runs on the cloud hub only."}

        svc = LicensingService(self.db)
        record = svc.find_by_key(license_key)
        if not record:
            return {"success": False, "message": "License key not found."}

        blocked = svc._check_license_record_blocks(record, "cloud")
        if blocked and record.company_id and record.company_id != company_id:
            return {"success": False, "message": blocked.get("message", "License unavailable.")}

        existing_code = (
            self.db.query(Company)
            .filter(Company.company_code == company_code, Company.id != company_id)
            .first()
        )
        if existing_code:
            return {
                "success": False,
                "message": f"Company code '{company_code}' is already used on the cloud hub.",
            }

        company = self.db.query(Company).filter(Company.id == company_id).first()
        if not company:
            company = Company(
                id=company_id,
                name=company_name,
                legal_name=legal_name or company_name,
                company_code=company_code,
                phone=phone,
                email=email,
                industry=industry,
                country=country,
                currency=currency,
                timezone=timezone,
                deployment_mode="cloud",
                is_active=True,
                is_setup_complete=True,
                setup_completed_at=datetime.utcnow(),
            )
            self.db.add(company)
        else:
            company.name = company_name
            company.legal_name = legal_name or company_name
            company.is_setup_complete = True
            company.is_active = True

        branch = self.db.query(Branch).filter(Branch.id == hq_branch_id).first()
        if not branch:
            branch = Branch(
                id=hq_branch_id,
                company_id=company_id,
                name=f"{company_name} — Head Office",
                code=hq_branch_code,
                phone=phone,
                email=email,
                country=country,
                is_head_office=True,
                active=True,
                currency=currency,
                timezone=timezone,
            )
            self.db.add(branch)

        admin = self.db.query(User).filter(User.id == admin_user_id).first()
        if not admin:
            conflict = (
                self.db.query(User)
                .filter(User.username == admin_username, User.id != admin_user_id)
                .first()
            )
            if conflict:
                return {
                    "success": False,
                    "message": f"Username '{admin_username}' already exists on the cloud hub.",
                }
            from app.utils.user_email import normalize_user_email

            admin = User(
                id=admin_user_id,
                company_id=company_id,
                branch_id=hq_branch_id,
                username=admin_username,
                password_digest=admin_password_digest,
                role="super_admin",
                email=normalize_user_email(admin_email),
                first_name=admin_first_name or admin_username,
                last_name=admin_last_name or "",
                active=True,
            )
            self.db.add(admin)
        else:
            admin.password_digest = admin_password_digest
            admin.company_id = company_id
            admin.branch_id = hq_branch_id
            admin.active = True

        if accounting_codes:
            from app.models.accounting import AccountingCode

            for row in accounting_codes:
                ac_id = row.get("id")
                code = row.get("code")
                if not ac_id or not code:
                    continue
                try:
                    ac = self.db.query(AccountingCode).filter(AccountingCode.id == ac_id).first()
                    if not ac:
                        ac = AccountingCode(
                            id=ac_id,
                            code=code,
                            name=row.get("name") or code,
                            account_type=row.get("account_type") or "Asset",
                            category=row.get("category") or "Cash",
                            branch_id=hq_branch_id,
                        )
                        self.db.add(ac)
                    else:
                        ac.name = row.get("name") or ac.name or code
                        if row.get("account_type"):
                            ac.account_type = row["account_type"]
                        if row.get("category"):
                            ac.category = row["category"]
                        ac.is_parent = bool(row.get("is_parent", False))
                        ac.reporting_tag = row.get("reporting_tag")
                        ac.branch_id = hq_branch_id
                    if row.get("balance") is not None:
                        ac.balance = Decimal(str(row.get("balance") or 0))
                    self.db.flush()
                except ValueError as exc:
                    logger.warning("Skip mirror account %s: %s", code, exc)
                    if "ac" in locals() and ac in self.db:
                        self.db.expunge(ac)
                    continue

        now = datetime.utcnow()
        record.status = "active"
        record.company_id = company_id
        record.activated_at = record.activated_at or now
        if site_name:
            note = f"Cloud mirror of local site: {site_name}"
            record.notes = f"{record.notes}\n{note}".strip() if record.notes else note

        company.license_key_id = record.id
        company.license_expires_at = record.valid_until
        company.subscription_plan = record.plan or company.subscription_plan

        self.db.flush()

        cloud_url = (settings.erp_public_url or settings.license_cloud_url or "").rstrip("/")
        return {
            "success": True,
            "message": "Cloud mirror tenant ready. Use the same company code and admin login on the cloud hub.",
            "company_id": company_id,
            "company_code": company_code,
            "cloud_login_url": f"{cloud_url}/static/login.html" if cloud_url else None,
        }


def _write_license_node_file(cfg: Dict[str, Any]) -> None:
    path = Path(settings.cloud_sync_config_path or "deploy/license-node.json")
    path.parent.mkdir(parents=True, exist_ok=True)
    path.write_text(json.dumps(cfg, indent=2) + "\n", encoding="utf-8")


def link_local_server_to_cloud(
    *,
    license_key: str,
    cloud_url: str,
    setup_token: Optional[str] = None,
    mirror_provision_token: Optional[str] = None,
    company_id: str,
    company_code: str,
    company_name: str,
    admin_user_id: str,
    admin_username: str,
    admin_password_digest: str,
    hq_branch_id: str,
    hq_branch_code: str,
    legal_name: Optional[str] = None,
    admin_email: Optional[str] = None,
    admin_first_name: Optional[str] = None,
    admin_last_name: Optional[str] = None,
    phone: Optional[str] = None,
    email: Optional[str] = None,
    industry: Optional[str] = None,
    currency: str = "BWP",
    accounting_codes: Optional[List[Dict[str, Any]]] = None,
    public_base_url: Optional[str] = None,
    node_name: Optional[str] = None,
) -> Dict[str, Any]:
    """
    Called on local/hybrid server after setup: provision cloud tenant, register sync node, save config.
  """
    base = (cloud_url or settings.license_cloud_url or "").rstrip("/")
    if not base:
        return {"success": False, "message": "LICENSE_CLOUD_URL is not configured."}

    payload = {
        "license_key": license_key,
        "deployment": "local",
        "setup_token": setup_token,
        "mirror_provision_token": mirror_provision_token,
        "company_id": company_id,
        "company_code": company_code,
        "company_name": company_name,
        "legal_name": legal_name,
        "admin_user_id": admin_user_id,
        "admin_username": admin_username,
        "admin_password_digest": admin_password_digest,
        "hq_branch_id": hq_branch_id,
        "hq_branch_code": hq_branch_code,
        "admin_email": admin_email,
        "admin_first_name": admin_first_name,
        "admin_last_name": admin_last_name,
        "phone": phone,
        "email": email,
        "industry": industry,
        "currency": currency,
        "accounting_codes": accounting_codes or [],
        "site_name": company_name,
    }

    result: Dict[str, Any] = {"success": False, "provision": None, "node": None}
    try:
        with httpx.Client(timeout=90.0) as client:
            prov = client.post(f"{base}/api/v1/licensing/provision-cloud-mirror", json=payload)
            if prov.status_code >= 400:
                result["message"] = prov.text
                return result
            result["provision"] = prov.json()
            if not result["provision"].get("success"):
                result["message"] = result["provision"].get("message", "Cloud provisioning failed")
                return result

            reg = client.post(
                f"{base}/api/v1/licensing/nodes/register",
                json={
                    "license_key": license_key,
                    "node_name": node_name or "CNPERP Local",
                    "public_base_url": public_base_url,
                    "machine_fingerprint": company_id[:32],
                    "app_version": getattr(settings, "app_name", "CNPERP"),
                },
            )
            if reg.status_code >= 400:
                result["message"] = f"Cloud tenant created but node registration failed: {reg.text}"
                result["success"] = True
                return result
            node_data = reg.json()
            result["node"] = node_data
            cfg = {
                "cloud_url": base,
                "license_key": license_key,
                "node_name": node_name or "CNPERP Local",
                "public_base_url": public_base_url or "",
                "machine_fingerprint": company_id[:32],
                "node_id": node_data.get("node_id", ""),
                "sync_token": node_data.get("sync_token", ""),
            }
            _write_license_node_file(cfg)
            result["success"] = True
            result["message"] = (
                "Cloud backup mirror created. Same company code and admin password work on the cloud hub. "
                "Transaction sync is enabled."
            )
            result["cloud_login_url"] = result["provision"].get("cloud_login_url")
    except httpx.RequestError as exc:
        result["message"] = f"Cannot reach cloud hub: {exc}"
    return result


def link_local_server_after_setup(*, license_key: str, setup_token: str, cloud_url: str, **kwargs) -> Dict[str, Any]:
    """Setup wizard — uses short-lived setup_token."""
    return link_local_server_to_cloud(
        license_key=license_key,
        setup_token=setup_token,
        cloud_url=cloud_url,
        **kwargs,
    )


def provision_mirror_from_local_db(
    db: Session,
    current_user: User,
    *,
    license_key_override: Optional[str] = None,
    public_base_url: Optional[str] = None,
) -> Dict[str, Any]:
    """Existing LAN install — push company snapshot to cloud hub (Settings button)."""
    if (settings.deployment_mode or "cloud").lower() not in ("local", "hybrid"):
        return {"success": False, "message": "Cloud mirror provisioning is for local/hybrid servers only."}

    company_id = getattr(current_user, "company_id", None)
    if not company_id:
        return {"success": False, "message": "No company on this account."}

    company = db.query(Company).filter(Company.id == company_id).first()
    if not company or not company.is_setup_complete:
        return {"success": False, "message": "Company setup is not complete on this server."}

    admin = (
        db.query(User)
        .filter(User.company_id == company_id, User.id == current_user.id, User.active == True)
        .first()
    )
    if not admin or (admin.role or "").lower() not in ("super_admin", "admin"):
        admin = (
            db.query(User)
            .filter(User.company_id == company_id, User.role == "super_admin", User.active == True)
            .first()
        )
    if not admin:
        return {"success": False, "message": "No administrator user found for this company."}

    hq = (
        db.query(Branch)
        .filter(Branch.company_id == company_id, Branch.is_head_office == True)
        .first()
    )
    if not hq:
        hq = db.query(Branch).filter(Branch.company_id == company_id).first()
    if not hq:
        return {"success": False, "message": "No branch found for this company."}

    cfg = load_node_config()
    license_key = (license_key_override or cfg.get("license_key") or "").strip()
    if not license_key:
        return {
            "success": False,
            "message": "License key required. Add it to deploy/license-node.json or enter it below.",
        }

    from app.models.accounting import AccountingCode

    accounting_codes = [
        {
            "id": ac.id,
            "code": ac.code,
            "name": ac.name,
            "account_type": ac.account_type,
            "category": ac.category,
            "is_parent": ac.is_parent,
            "reporting_tag": ac.reporting_tag,
            "balance": float(ac.balance or 0),
        }
        for ac in db.query(AccountingCode).filter(AccountingCode.branch_id == hq.id).all()
    ]

    mirror_token = create_mirror_provision_token(company_id, license_key)
    cloud_url = cfg.get("cloud_url") or settings.license_cloud_url or ""

    return link_local_server_to_cloud(
        license_key=license_key,
        mirror_provision_token=mirror_token,
        cloud_url=cloud_url,
        company_id=company.id,
        company_code=company.company_code,
        company_name=company.name,
        admin_user_id=admin.id,
        admin_username=admin.username,
        admin_password_digest=admin.password_digest,
        hq_branch_id=hq.id,
        hq_branch_code=hq.code,
        legal_name=company.legal_name,
        admin_email=admin.email,
        admin_first_name=admin.first_name,
        admin_last_name=admin.last_name,
        phone=company.phone,
        email=company.email,
        industry=company.industry,
        currency=company.currency or "BWP",
        accounting_codes=accounting_codes,
        public_base_url=public_base_url,
        node_name=cfg.get("node_name") or company.name,
    )
