from __future__ import annotations

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

from sqlalchemy import or_, select
from sqlalchemy.exc import OperationalError, ProgrammingError
from sqlalchemy.ext.asyncio import AsyncSession

from ..auth_security import _ensure_clerk_account_and_user
from ..models import (
    Account,
    AccountEntitlements,
    LicenseGrant,
    LicenseGrantStatus,
    LicenseGrantType,
    UsageQuotaModel,
    User,
)
from . import license_manager as license_manager_service

_SHEETS_PLAN_KEYS = {"free", "pro", "scale", "unleashed"}


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 _normalize_email(value: Optional[str]) -> Optional[str]:
    normalized = (value or "").strip().lower() or None
    if not normalized or normalized.endswith("@unknown"):
        return None
    return normalized


def _display_status(grant: LicenseGrant) -> str:
    normalized = license_manager_service.normalize_license_status(
        grant.status,
        ends_at=grant.ends_at,
    )
    if normalized == LicenseGrantStatus.CANCELED.value:
        return "canceled"
    if normalized in {
        LicenseGrantStatus.EXPIRED.value,
        LicenseGrantStatus.REVOKED.value,
        LicenseGrantStatus.PENDING.value,
    }:
        return "expired"
    return "active"


def _normalize_sheets_plan(plan_key: Optional[str]) -> str:
    normalized = str(plan_key or "").strip().lower()
    if normalized in _SHEETS_PLAN_KEYS:
        return normalized
    return "free"


def _effective_tier(grant: LicenseGrant) -> str:
    normalized_plan = _normalize_sheets_plan(grant.plan_key)
    display_status = _display_status(grant)
    if display_status != "active":
        return "free"
    return normalized_plan


def _license_type(grant: LicenseGrant) -> str:
    normalized = str(grant.grant_type or "").strip().lower()
    if normalized == LicenseGrantType.PAID.value:
        return "paid"
    return "trial"


def serialize_canonical_sheets_license(grant: LicenseGrant) -> dict[str, Any]:
    display_status = _display_status(grant)
    license_type = _license_type(grant)
    trial_ends_at = _coerce_datetime(grant.ends_at) if license_type == "trial" else None
    days_remaining: Optional[int] = None
    if trial_ends_at and display_status == "active":
        seconds_remaining = (trial_ends_at - _now_utc()).total_seconds()
        if seconds_remaining > 0:
            days_remaining = int(seconds_remaining // 86400)
            if seconds_remaining % 86400:
                days_remaining += 1
        else:
            days_remaining = 0

    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": license_manager_service.normalize_license_status(
            grant.status,
            ends_at=grant.ends_at,
        ),
        "planKey": _normalize_sheets_plan(grant.plan_key),
        "tier": _normalize_sheets_plan(grant.plan_key),
        "effectiveTier": _effective_tier(grant),
        "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,
    }


async def _load_user_by_identity(
    db: AsyncSession,
    *,
    clerk_user_id: str,
    primary_email: Optional[str],
) -> Optional[User]:
    by_id = await db.get(User, clerk_user_id)
    if by_id is not None:
        return by_id

    result = await db.execute(select(User).where(User.clerk_user_id == clerk_user_id))
    by_clerk = result.scalar_one_or_none()
    if by_clerk is not None:
        return by_clerk

    normalized_email = _normalize_email(primary_email)
    if not normalized_email:
        return None

    result = await db.execute(select(User).where(User.email == normalized_email))
    return result.scalar_one_or_none()


def _default_account_name(primary_email: Optional[str], clerk_user_id: str) -> str:
    normalized_email = _normalize_email(primary_email)
    if normalized_email and "@" in normalized_email:
        domain = normalized_email.split("@", 1)[1].split(".", 1)[0]
        if domain:
            return domain.replace("-", " ").replace("_", " ").title()
    return f"Sheets User {clerk_user_id[:12]}"


async def ensure_sheets_account_context(
    db: AsyncSession,
    *,
    clerk_user_id: str,
    clerk_org_id: Optional[str],
    primary_email: Optional[str],
) -> tuple[str, Optional[Account], Optional[User]]:
    normalized_user_id = clerk_user_id.strip()
    normalized_org_id = (clerk_org_id or "").strip() or None
    if normalized_org_id == normalized_user_id:
        normalized_org_id = None
    normalized_email = _normalize_email(primary_email)

    if normalized_org_id:
        account_result = await db.execute(
            select(Account).where(
                or_(Account.id == normalized_org_id, Account.clerk_org_id == normalized_org_id)
            )
        )
        account = account_result.scalar_one_or_none()
        try:
            if account is None:
                await _ensure_clerk_account_and_user(
                    db,
                    org_id=normalized_org_id,
                    user_id=normalized_user_id,
                    email=normalized_email,
                    logger=license_manager_service.log,
                )
        except Exception:
            await db.rollback()
        if account is None:
            account_result = await db.execute(
                select(Account).where(
                    or_(Account.id == normalized_org_id, Account.clerk_org_id == normalized_org_id)
                )
            )
            account = account_result.scalar_one_or_none()
        user = await _load_user_by_identity(
            db,
            clerk_user_id=normalized_user_id,
            primary_email=normalized_email,
        )
        if account is not None:
            return str(account.id), account, user

    user = await _load_user_by_identity(
        db,
        clerk_user_id=normalized_user_id,
        primary_email=normalized_email,
    )
    if user is not None and user.account_id:
        account = await db.get(Account, user.account_id)
        return str(user.account_id), account, user

    account_id = normalized_org_id or normalized_user_id
    account = await db.get(Account, account_id)
    dirty = False

    if account is None:
        account = Account(
            id=account_id,
            name=_default_account_name(normalized_email, normalized_user_id),
            clerk_org_id=normalized_org_id,
        )
        db.add(account)
        dirty = True
    elif normalized_org_id and account.clerk_org_id != normalized_org_id:
        account.clerk_org_id = normalized_org_id
        db.add(account)
        dirty = True

    if user is None:
        user = User(
            id=normalized_user_id,
            account_id=account_id,
            email=normalized_email or f"{normalized_user_id}@unknown",
            clerk_user_id=normalized_user_id,
        )
        db.add(user)
        dirty = True
    else:
        if user.account_id != account_id:
            user.account_id = account_id
            dirty = True
        if normalized_email and user.email != normalized_email:
            user.email = normalized_email
            dirty = True
        if user.clerk_user_id != normalized_user_id:
            user.clerk_user_id = normalized_user_id
            dirty = True
        if dirty:
            db.add(user)

    if dirty:
        await db.commit()
        if account is not None:
            await db.refresh(account)
        if user is not None:
            await db.refresh(user)

    return account_id, account, user


async def resolve_canonical_sheets_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 = _normalize_email(primary_email)
    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 == "sheets")
            .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 == "sheets")
            .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


async def _derive_legacy_sheets_seed(
    db: AsyncSession,
    *,
    account_id: str,
) -> dict[str, Any]:
    quota = await db.get(UsageQuotaModel, account_id)
    entitlements = await db.get(AccountEntitlements, account_id)

    quota_tier = str(getattr(quota, "tier", "") or "").strip().lower()
    entitlements_tier = str(getattr(entitlements, "tier", "") or "").strip().lower()
    plan_key = _normalize_sheets_plan(quota_tier or entitlements_tier or "free")
    ends_at = _coerce_datetime(getattr(entitlements, "expires_at", None))
    starts_at = _coerce_datetime(getattr(entitlements, "trial_started_at", None))
    raw_status = str(getattr(quota, "subscription_status", "") or "").strip().lower()
    if not raw_status and ends_at:
        raw_status = "trialing"
    status = license_manager_service.normalize_license_status(raw_status or "active", ends_at=ends_at)

    if raw_status == "trialing" or ends_at is not None:
        grant_type = LicenseGrantType.TRIAL.value
    elif plan_key != "free":
        grant_type = LicenseGrantType.PAID.value
    else:
        grant_type = LicenseGrantType.MANUAL_OVERRIDE.value

    return {
        "grant_type": grant_type,
        "plan_key": plan_key,
        "status": status,
        "source": str(getattr(quota, "tier_sync_source", "") or getattr(entitlements, "source", "") or "sheets_self_service"),
        "starts_at": starts_at,
        "ends_at": ends_at,
        "attributes": {
            "legacy_quota_tier": quota_tier or None,
            "legacy_subscription_status": raw_status or None,
            "legacy_entitlement_tier": entitlements_tier or None,
            "legacy_monthly_match_entitlement": getattr(quota, "monthly_match_entitlement", None),
            "legacy_monthly_rows": getattr(entitlements, "monthly_row_limit", None),
            "legacy_byo_enabled": getattr(entitlements, "byo_enabled", None),
        },
    }


async def ensure_canonical_sheets_grant(
    db: AsyncSession,
    *,
    account_id: str,
    user: Optional[User],
    clerk_user_id: str,
    primary_email: Optional[str],
) -> Optional[LicenseGrant]:
    resolved_account_id, existing = await resolve_canonical_sheets_grant(
        db,
        account_id=account_id,
        clerk_user_id=clerk_user_id,
        primary_email=primary_email,
    )
    if existing is not None:
        return existing

    seed = await _derive_legacy_sheets_seed(db, account_id=resolved_account_id or account_id)
    try:
        grant, _projection = await license_manager_service.upsert_and_sync_license_grant(
            db,
            account_id=resolved_account_id or account_id,
            product_surface="sheets",
            user_id=user.id if user else None,
            clerk_user_id=clerk_user_id,
            primary_email=_normalize_email(primary_email),
            grant_type=str(seed["grant_type"]),
            plan_key=str(seed["plan_key"]),
            status=str(seed["status"]),
            source=str(seed["source"]),
            starts_at=seed["starts_at"],
            ends_at=seed["ends_at"],
            attributes=dict(seed["attributes"]),
        )
        return grant
    except (OperationalError, ProgrammingError):
        return None


async def ensure_canonical_sheets_access(
    db: AsyncSession,
    *,
    clerk_user_id: str,
    clerk_org_id: Optional[str],
    primary_email: Optional[str],
) -> tuple[str, Optional[Account], Optional[User], Optional[LicenseGrant]]:
    account_id, account, user = await ensure_sheets_account_context(
        db,
        clerk_user_id=clerk_user_id,
        clerk_org_id=clerk_org_id,
        primary_email=primary_email,
    )
    grant = await ensure_canonical_sheets_grant(
        db,
        account_id=account_id,
        user=user,
        clerk_user_id=clerk_user_id,
        primary_email=primary_email,
    )
    if grant is not None:
        account_id = grant.account_id
    return account_id, account, user, grant
