from __future__ import annotations

from datetime import datetime, timezone
from typing import Any, Optional

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

from ..models import Account, LicenseGrant, LicenseGrantStatus, LicenseGrantType, User
from .gremlin_license_gateway import normalize_gremlin_status

_SITE_TIER_MAP = {
    "": "local_free",
    "free": "local_free",
    "local_free": "local_free",
    "community": "local_free",
    "workspace_ops": "workspace_ops",
    "pro": "workspace_ops",
    "hubspot_admin": "hubspot_admin",
    "crm_engineer": "hubspot_admin",
    "full_stack": "full_stack",
    "platform": "full_stack",
    "scale": "full_stack",
    "unleashed": "full_stack",
}


def _now_utc() -> datetime:
    return datetime.now(timezone.utc)


def _coerce_datetime(value: object) -> Optional[datetime]:
    if value is None:
        return None
    if isinstance(value, datetime):
        if value.tzinfo is None:
            return value.replace(tzinfo=timezone.utc)
        return value.astimezone(timezone.utc)
    if isinstance(value, str):
        candidate = value.strip()
        if not candidate:
            return None
        try:
            parsed = datetime.fromisoformat(candidate.replace("Z", "+00:00"))
        except ValueError:
            return None
        if parsed.tzinfo is None:
            return parsed.replace(tzinfo=timezone.utc)
        return parsed.astimezone(timezone.utc)
    return None


def _iso_or_none(value: object) -> Optional[str]:
    parsed = _coerce_datetime(value)
    if parsed is None:
        return None
    return parsed.isoformat()


def _tier_after_expiry(grant: LicenseGrant) -> str:
    raw = None
    if isinstance(grant.attributes, dict):
        raw = grant.attributes.get("tier_after_expiry")
    normalized = _SITE_TIER_MAP.get(str(raw or "").strip().lower())
    if normalized:
        return normalized
    if str(grant.grant_type or "").strip().lower() == LicenseGrantType.TRIAL.value:
        return "local_free"
    return "local_free"


def canonical_gremlin_plan_to_site_tier(
    *,
    plan_key: Optional[str],
    status: Optional[str],
    ends_at: object = None,
    tier_after_expiry: Optional[str] = None,
) -> str:
    normalized_status = normalize_gremlin_status(status, ends_at=_coerce_datetime(ends_at))
    normalized_plan = _SITE_TIER_MAP.get(str(plan_key or "").strip().lower(), "full_stack")
    expiry_fallback = _SITE_TIER_MAP.get(str(tier_after_expiry or "").strip().lower(), "local_free")
    parsed_ends_at = _coerce_datetime(ends_at)

    if normalized_status in {
        LicenseGrantStatus.REVOKED.value,
        LicenseGrantStatus.EXPIRED.value,
        LicenseGrantStatus.PENDING.value,
    }:
        return expiry_fallback
    if normalized_status == LicenseGrantStatus.CANCELED.value:
        if parsed_ends_at and parsed_ends_at <= _now_utc():
            return expiry_fallback
        return normalized_plan
    return normalized_plan


def serialize_canonical_gremlin_license(grant: LicenseGrant) -> dict[str, Any]:
    normalized_status = normalize_gremlin_status(grant.status, ends_at=grant.ends_at)
    license_type = (
        "paid"
        if str(grant.grant_type or "").strip().lower() == LicenseGrantType.PAID.value
        else "trial"
    )
    tier_after_expiry = _tier_after_expiry(grant)
    effective_tier = canonical_gremlin_plan_to_site_tier(
        plan_key=grant.plan_key,
        status=normalized_status,
        ends_at=grant.ends_at,
        tier_after_expiry=tier_after_expiry,
    )
    display_status = "active"
    if normalized_status == LicenseGrantStatus.CANCELED.value:
        display_status = "canceled"
    elif normalized_status != LicenseGrantStatus.ACTIVE.value:
        display_status = "expired"

    trial_ends_at = _coerce_datetime(grant.ends_at) if license_type == "trial" else None
    days_remaining: Optional[int] = None
    if license_type == "trial" and display_status == "active" and trial_ends_at:
        days_remaining = max(
            0,
            int((trial_ends_at - _now_utc()).total_seconds() // 86400),
        )
        if (trial_ends_at - _now_utc()).total_seconds() % 86400:
            days_remaining += 1

    has_billing_portal = (
        license_type == "paid"
        and effective_tier != "local_free"
        and effective_tier != "full_stack"
    )

    return {
        "accountId": grant.account_id,
        "userId": grant.user_id,
        "clerkUserId": grant.clerk_user_id,
        "primaryEmail": grant.primary_email,
        "licenseType": license_type,
        "status": display_status,
        "rawStatus": normalized_status,
        "planKey": str(grant.plan_key or "").strip().lower() or "local_free",
        "tier": str(grant.plan_key or "").strip().lower() or "local_free",
        "tierAfterExpiry": tier_after_expiry,
        "effectiveTier": effective_tier,
        "trialStartedAt": _iso_or_none(grant.starts_at) if license_type == "trial" else None,
        "trialEndsAt": _iso_or_none(grant.ends_at) if license_type == "trial" else None,
        "daysRemaining": days_remaining,
        "currentPeriodStart": _iso_or_none(grant.starts_at) if license_type == "paid" else None,
        "currentPeriodEnd": _iso_or_none(grant.ends_at) if license_type == "paid" else None,
        "hasBillingPortal": has_billing_portal,
    }


async def resolve_canonical_gremlin_grant(
    db: AsyncSession,
    *,
    account_id: Optional[str] = None,
    clerk_user_id: Optional[str] = None,
    primary_email: Optional[str] = None,
) -> tuple[Optional[str], Optional[LicenseGrant]]:
    normalized_email = (primary_email or "").strip().lower() or None
    canonical_account_id: Optional[str] = None

    if account_id:
        result = await db.execute(
            select(Account.id)
            .where(or_(Account.id == account_id, Account.clerk_org_id == account_id))
            .limit(1)
        )
        row = result.first()
        canonical_account_id = str(row[0]) if row and row[0] else str(account_id)

    if canonical_account_id is None and clerk_user_id:
        result = await db.execute(
            select(User.account_id).where(User.clerk_user_id == clerk_user_id).limit(1)
        )
        row = result.first()
        if row and row[0]:
            canonical_account_id = str(row[0])

    if canonical_account_id is None and normalized_email:
        result = await db.execute(
            select(User.account_id).where(User.email == normalized_email).limit(1)
        )
        row = result.first()
        if row and row[0]:
            canonical_account_id = str(row[0])

    if canonical_account_id:
        result = await db.execute(
            select(LicenseGrant)
            .where(LicenseGrant.account_id == canonical_account_id)
            .where(LicenseGrant.product_surface == "gremlin")
            .limit(1)
        )
        grant = result.scalar_one_or_none()
        if grant is not None:
            return canonical_account_id, grant

    lookup_conditions = []
    if clerk_user_id:
        lookup_conditions.append(LicenseGrant.clerk_user_id == clerk_user_id)
    if normalized_email:
        lookup_conditions.append(LicenseGrant.primary_email == normalized_email)

    if lookup_conditions:
        result = await db.execute(
            select(LicenseGrant)
            .where(LicenseGrant.product_surface == "gremlin")
            .where(or_(*lookup_conditions))
            .order_by(LicenseGrant.updated_at.desc())
        )
        grant = result.scalars().first()
        if grant is not None:
            return grant.account_id, grant

    return canonical_account_id, None
