"""Commercial resolvers — bundle / plan / license / entitlement."""
from __future__ import annotations

import uuid
from datetime import datetime, timedelta, timezone
from typing import Any

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from app.commercial.kinds import KIND_BY_KIND
from app.commercial.repository import CommercialRegistryRepository
from app.commercial.service import CommercialRegistryService
from app.models.commercial import (
    CommercialActivationState,
    CommercialTenantLicense,
    CommercialTenantSubscription,
)
from app.models.events import OutboxEvent
from shared.events import EventStatus
from shared.exceptions import NotFoundError, ValidationAppError


def _aware(dt: datetime | None) -> datetime | None:
    if dt is None:
        return None
    if dt.tzinfo is None:
        return dt.replace(tzinfo=timezone.utc)
    return dt


class BundleResolver:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db
        self.repo = CommercialRegistryRepository(db)
        self.registry = CommercialRegistryService(db)

    async def resolve(self, bundle_code: str) -> dict[str, Any]:
        bundle = await self.repo.get_by_code("bundles", bundle_code)
        if bundle is None or bundle.status != "published":
            raise NotFoundError(f"باندل «{bundle_code}» یافت نشد")
        payload = dict(bundle.payload or {})
        product_codes = list(payload.get("product_codes") or [])
        capability_codes = list(payload.get("capability_codes") or [])
        pricing_refs = list(payload.get("pricing_refs") or [])
        extension_codes = list(payload.get("extension_codes") or [])
        asset_codes = list(payload.get("asset_codes") or [])
        automation_codes = list(payload.get("automation_pack_codes") or [])
        policy_codes = list(payload.get("policy_codes") or [])
        services = list(payload.get("services") or [])

        async def load_many(kind: str, codes: list[str]) -> list[dict[str, Any]]:
            out: list[dict[str, Any]] = []
            code_field = (KIND_BY_KIND.get(kind).code_field if kind in KIND_BY_KIND else "code")
            for code in codes:
                if isinstance(code, dict):
                    code = (
                        code.get("code")
                        or code.get("product_code")
                        or code.get("capability_code")
                        or code.get(code_field)
                    )
                if not code:
                    continue
                obj = await self.repo.get_by_code(kind, str(code))
                if obj and obj.status == "published":
                    out.append(self.registry.serialize(obj, code_field))
            return out

        return {
            "bundle_code": bundle.stable_code,
            "display_name": bundle.display_name,
            "version_code": bundle.version_code,
            "products": await load_many("products", product_codes),
            "capabilities": await load_many("capabilities", capability_codes),
            "pricing": await load_many(
                "pricing",
                [
                    (r.get("pricing_item_code") if isinstance(r, dict) else r)
                    for r in pricing_refs
                ],
            ),
            "extensions": await load_many("extensions", extension_codes),
            "assets": await load_many("assets", asset_codes),
            "automation_packs": await load_many("automation_packs", automation_codes),
            "policies": await load_many("policies", policy_codes),
            "services": services,
            "payload": payload,
            "metadata": bundle.meta_data or {},
        }


class PlanResolver:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db
        self.repo = CommercialRegistryRepository(db)
        self.registry = CommercialRegistryService(db)

    async def resolve(self, plan_code: str) -> dict[str, Any]:
        plan = await self.repo.get_by_code("plans", plan_code)
        if plan is None:
            # fallback: treat pricing item as plan shell
            plan = await self.repo.get_by_code("pricing", plan_code)
        if plan is None or plan.status != "published":
            raise NotFoundError(f"پلن «{plan_code}» یافت نشد")
        payload = dict(plan.payload or {})
        trial = payload.get("trial") or {}
        return {
            "plan_code": plan.stable_code,
            "display_name": plan.display_name,
            "trial": {
                "eligible": bool(payload.get("trial_eligible") or trial.get("eligible")),
                "days": trial.get("days") or payload.get("trial_days") or 14,
            },
            "limits": payload.get("limits") or payload.get("quotas") or {},
            "price": {
                "amount_minor": payload.get("amount_minor"),
                "currency": payload.get("currency"),
                "pricing_model": payload.get("pricing_model"),
                "pricing_item_code": payload.get("pricing_item_code") or plan.stable_code,
            },
            "renewal": payload.get("renewal") or {"interval": payload.get("interval") or "monthly"},
            "visibility": plan.visibility,
            "bundle_code": payload.get("bundle_code"),
            "payload": payload,
        }


class LicenseResolver:
    def __init__(self, db: AsyncSession) -> None:
        self.db = db
        self.bundle_resolver = BundleResolver(db)
        self.plan_resolver = PlanResolver(db)

    async def get_subscription(self, tenant_id: uuid.UUID) -> CommercialTenantSubscription | None:
        stmt = (
            select(CommercialTenantSubscription)
            .where(CommercialTenantSubscription.tenant_id == tenant_id)
            .order_by(CommercialTenantSubscription.updated_at.desc())
        )
        return (await self.db.execute(stmt)).scalars().first()

    async def list_licenses(self, tenant_id: uuid.UUID) -> list[CommercialTenantLicense]:
        stmt = select(CommercialTenantLicense).where(
            CommercialTenantLicense.tenant_id == tenant_id
        )
        return list((await self.db.execute(stmt)).scalars().all())

    async def activate(
        self,
        tenant_id: uuid.UUID,
        *,
        plan_code: str | None = None,
        bundle_code: str | None = None,
        pricing_item_code: str | None = None,
        status: str = "trialing",
        actor: uuid.UUID | None = None,
    ) -> dict[str, Any]:
        if not any([plan_code, bundle_code, pricing_item_code]):
            raise ValidationAppError("یکی از plan_code / bundle_code / pricing_item_code لازم است")

        resolved_bundle = None
        resolved_plan = None
        if bundle_code:
            resolved_bundle = await self.bundle_resolver.resolve(bundle_code)
        if plan_code or pricing_item_code:
            resolved_plan = await self.plan_resolver.resolve(plan_code or pricing_item_code)  # type: ignore[arg-type]

        now = datetime.now(timezone.utc)
        trial_days = 0
        if resolved_plan:
            trial = resolved_plan.get("trial") or {}
            trial_days = int(trial.get("days") or resolved_plan.get("trial_days") or 0)
        trial_ends = now + timedelta(days=trial_days) if status == "trialing" and trial_days > 0 else None

        sub = await self.get_subscription(tenant_id)
        if sub is None:
            sub = CommercialTenantSubscription(tenant_id=tenant_id)
            self.db.add(sub)
        sub.plan_code = plan_code or (resolved_plan or {}).get("plan_code")
        sub.bundle_code = bundle_code or (resolved_plan or {}).get("bundle_code")
        sub.pricing_item_code = pricing_item_code
        sub.status = status
        sub.trial_ends_at = trial_ends
        sub.current_period_start = now
        sub.current_period_end = trial_ends or (now + timedelta(days=30))
        sub.created_by = actor
        sub.meta_data = {
            "bundle": resolved_bundle,
            "plan": resolved_plan,
        }

        capability_codes: list[str] = []
        product_codes: list[str] = []
        if resolved_bundle:
            capability_codes = [c.get("capability_code") or c.get("stable_code") for c in resolved_bundle.get("capabilities", [])]
            product_codes = [p.get("product_code") or p.get("stable_code") for p in resolved_bundle.get("products", [])]
        capability_codes = [c for c in capability_codes if c]
        product_codes = [p for p in product_codes if p]

        license_code = f"lic.{tenant_id}.{sub.bundle_code or sub.plan_code or 'default'}"
        lic_stmt = select(CommercialTenantLicense).where(
            CommercialTenantLicense.tenant_id == tenant_id,
            CommercialTenantLicense.license_code == license_code,
        )
        lic = (await self.db.execute(lic_stmt)).scalar_one_or_none()
        if lic is None:
            lic = CommercialTenantLicense(
                tenant_id=tenant_id,
                license_code=license_code,
            )
            self.db.add(lic)
        lic.display_name = (resolved_bundle or {}).get("display_name") or (resolved_plan or {}).get("display_name")
        lic.status = "active"
        lic.capability_codes = capability_codes
        lic.product_codes = product_codes
        lic.bundle_code = sub.bundle_code
        lic.quota = (resolved_plan or {}).get("limits") or {}
        lic.expires_at = sub.current_period_end

        await self._ensure_activation(tenant_id)
        self.db.add(
            OutboxEvent(
                event_type="commercial.subscription.activated",
                aggregate_type="commercial.subscription",
                aggregate_id=str(sub.id) if sub.id else str(tenant_id),
                tenant_id=tenant_id,
                payload={
                    "tenant_id": str(tenant_id),
                    "status": status,
                    "bundle_code": sub.bundle_code,
                    "plan_code": sub.plan_code,
                },
                status=EventStatus.PENDING,
            )
        )
        await self.db.commit()
        await self.db.refresh(sub)
        return {
            "subscription": {
                "id": str(sub.id),
                "tenant_id": str(tenant_id),
                "status": sub.status,
                "plan_code": sub.plan_code,
                "bundle_code": sub.bundle_code,
                "pricing_item_code": sub.pricing_item_code,
                "trial_ends_at": sub.trial_ends_at.isoformat() if sub.trial_ends_at else None,
                "current_period_end": sub.current_period_end.isoformat() if sub.current_period_end else None,
            },
            "license": {
                "license_code": lic.license_code,
                "status": lic.status,
                "capability_codes": lic.capability_codes,
                "product_codes": lic.product_codes,
            },
        }

    async def check_entitlement(
        self, tenant_id: uuid.UUID, feature_key: str
    ) -> dict[str, Any]:
        licenses = await self.list_licenses(tenant_id)
        sub = await self.get_subscription(tenant_id)
        active_caps: set[str] = set()
        required_bundle = None
        for lic in licenses:
            if lic.status != "active":
                continue
            if lic.expires_at and _aware(lic.expires_at) < datetime.now(timezone.utc):
                continue
            active_caps.update(str(c) for c in (lic.capability_codes or []))
            required_bundle = lic.bundle_code or required_bundle

        # Also accept feature_key / product_code / hospitality.* style
        has = (
            feature_key in active_caps
            or feature_key.replace("hospitality.", "") in active_caps
            or any(feature_key.endswith(c) or c.endswith(feature_key) for c in active_caps)
        )
        if not licenses and sub and sub.status in ("trialing", "active"):
            # trial without detailed caps → allow unless explicitly denied later
            has = True
            reason = "اشتراک/trial فعال است"
        elif has:
            reason = "مجوز فعال"
        else:
            reason = "قابلیت در entitlement فعال نیست"

        return {
            "has_access": has,
            "allowed": has,
            "reason": reason,
            "required_plan": sub.plan_code if sub and not has else None,
            "required_capability": feature_key if not has else None,
            "required_bundle": required_bundle if not has else None,
        }

    async def _ensure_activation(self, tenant_id: uuid.UUID) -> CommercialActivationState:
        stmt = select(CommercialActivationState).where(
            CommercialActivationState.tenant_id == tenant_id
        )
        state = (await self.db.execute(stmt)).scalar_one_or_none()
        if state is None:
            steps = [
                {"step_key": "workspace", "label": "Workspace", "status": "done"},
                {"step_key": "bundle", "label": "Bundle", "status": "done"},
                {"step_key": "trial", "label": "Trial", "status": "done"},
                {"step_key": "dashboard", "label": "Dashboard", "status": "pending", "href": "/dashboard"},
            ]
            state = CommercialActivationState(
                tenant_id=tenant_id,
                status="in_progress",
                completion_percent=75,
                steps=steps,
                checklist=[
                    {"id": "workspace", "label": "Workspace", "done": True},
                    {"id": "bundle", "label": "انتخاب باندل", "done": True},
                    {"id": "billing", "label": "Billing", "done": False, "href": "/billing"},
                    {"id": "domain", "label": "دامنه", "done": False, "href": "/domains"},
                ],
            )
            self.db.add(state)
        return state
