"""Planes, límites de uso y comprobaciones comerciales."""

from __future__ import annotations

from datetime import datetime, timezone

from fastapi import HTTPException
from sqlalchemy.orm import Session, joinedload

from app.models import Plan, Subscription, UsageCounter, User

PLAN_RANK = {
    "free": 0,
    "esencial": 1,
    "profesional": 2,
    "carrera_max": 3,
}

DEFAULT_ENTITLEMENTS = {
    "free": {
        "scrapes_per_day": 2,
        "max_sources": 15,
        "regions": ["es"],
        "max_cvs": 1,
        "ai_adapts_per_month": 3,
        "max_applications": 20,
        "apply_prepare": False,
        "apply_run": False,
        "all_sources": False,
        "intl_sources": False,
    },
    "esencial": {
        "scrapes_per_day": 10,
        "max_sources": 35,
        "regions": ["es"],
        "max_cvs": 3,
        "ai_adapts_per_month": 20,
        "max_applications": -1,
        "apply_prepare": False,
        "apply_run": False,
        "all_sources": False,
        "intl_sources": False,
    },
    "profesional": {
        "scrapes_per_day": 30,
        "max_sources": 60,
        "regions": ["es", "intl", "latam"],
        "max_cvs": 10,
        "ai_adapts_per_month": 100,
        "max_applications": -1,
        "apply_prepare": True,
        "apply_run": False,
        "all_sources": False,
        "intl_sources": True,
    },
    "carrera_max": {
        "scrapes_per_day": 9999,
        "max_sources": 9999,
        "regions": ["es", "intl", "latam"],
        "max_cvs": 9999,
        "ai_adapts_per_month": 9999,
        "max_applications": -1,
        "apply_prepare": True,
        "apply_run": True,
        "all_sources": True,
        "intl_sources": True,
    },
}


def _period_day() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%d")


def _period_month() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m")


def get_or_create_plan(db: Session, key: str) -> Plan:
    plan = db.query(Plan).filter(Plan.key == key).first()
    if plan:
        return plan
    raise HTTPException(404, f"Plan {key} no encontrado")


def ensure_user_subscription(db: Session, user: User, plan_key: str = "free") -> Subscription:
    sub = (
        db.query(Subscription)
        .options(joinedload(Subscription.plan))
        .filter(Subscription.user_id == user.id)
        .first()
    )
    if sub:
        return sub
    plan = get_or_create_plan(db, plan_key if not user.is_admin else "carrera_max")
    if user.is_admin:
        plan = get_or_create_plan(db, "carrera_max")
    sub = Subscription(user_id=user.id, plan_id=plan.id, status="active")
    db.add(sub)
    db.commit()
    db.refresh(sub)
    sub = (
        db.query(Subscription)
        .options(joinedload(Subscription.plan))
        .filter(Subscription.id == sub.id)
        .first()
    )
    return sub  # type: ignore[return-value]


def assign_plan(db: Session, user: User, plan: Plan, note: str = "") -> Subscription:
    sub = db.query(Subscription).filter(Subscription.user_id == user.id).first()
    if not sub:
        sub = Subscription(user_id=user.id, plan_id=plan.id, status="active", notes=note)
        db.add(sub)
    else:
        sub.plan_id = plan.id
        sub.status = "active"
        sub.notes = note or sub.notes
        sub.updated_at = datetime.now(timezone.utc)
    db.commit()
    db.refresh(sub)
    return sub


def get_plan_for_user(db: Session, user: User) -> Plan:
    if user.is_admin:
        plan = db.query(Plan).filter(Plan.key == "carrera_max").first()
        if plan:
            sub = db.query(Subscription).filter(Subscription.user_id == user.id).first()
            if not sub or sub.plan_id != plan.id:
                assign_plan(db, user, plan, note="Admin = Carrera Max")
            return plan
    sub = ensure_user_subscription(db, user, "free")
    if not sub.plan:
        return get_or_create_plan(db, "free")
    return sub.plan


def get_entitlements(db: Session, user: User) -> dict:
    plan = get_plan_for_user(db, user)
    base = dict(DEFAULT_ENTITLEMENTS.get(plan.key, DEFAULT_ENTITLEMENTS["free"]))
    custom = dict(plan.entitlements or {})
    base.update(custom)
    if user.is_admin:
        base = dict(DEFAULT_ENTITLEMENTS["carrera_max"])
        base.update(dict(plan.entitlements or {}))
        base["all_sources"] = True
        base["apply_prepare"] = True
        base["apply_run"] = True
    return base


def plan_rank(plan: Plan | None) -> int:
    if not plan:
        return 0
    return int(plan.sort_order if plan.sort_order is not None else PLAN_RANK.get(plan.key, 0))


def user_plan_rank(db: Session, user: User) -> int:
    if user.is_admin:
        return 99
    return plan_rank(get_plan_for_user(db, user))


def _get_counter(db: Session, user_id: int, metric: str, period_key: str) -> UsageCounter:
    row = (
        db.query(UsageCounter)
        .filter(
            UsageCounter.user_id == user_id,
            UsageCounter.metric == metric,
            UsageCounter.period_key == period_key,
        )
        .first()
    )
    if not row:
        row = UsageCounter(user_id=user_id, metric=metric, period_key=period_key, count=0)
        db.add(row)
        db.commit()
        db.refresh(row)
    return row


def get_usage(db: Session, user: User) -> dict:
    day = _get_counter(db, user.id, "scrapes_day", _period_day())
    month = _get_counter(db, user.id, "ai_adapts_month", _period_month())
    return {
        "scrapes_today": day.count,
        "ai_adapts_month": month.count,
        "period_day": day.period_key,
        "period_month": month.period_key,
    }


def increment_usage(db: Session, user: User, metric: str, amount: int = 1) -> int:
    period = _period_day() if metric == "scrapes_day" else _period_month()
    row = _get_counter(db, user.id, metric, period)
    row.count += amount
    row.updated_at = datetime.now(timezone.utc)
    db.commit()
    return row.count


def require_entitlement(db: Session, user: User, key: str, *, message: str | None = None) -> dict:
    ents = get_entitlements(db, user)
    if not ents.get(key):
        raise HTTPException(
            403,
            message or f"Tu plan no incluye «{key}». Mejora en /pricing o pide upgrade.",
        )
    return ents


def check_scrape_allowed(db: Session, user: User) -> dict:
    ents = get_entitlements(db, user)
    usage = get_usage(db, user)
    limit = int(ents.get("scrapes_per_day", 0))
    if usage["scrapes_today"] >= limit:
        raise HTTPException(
            403,
            f"Límite de scrapes diarios alcanzado ({limit}). Mejora tu plan en /pricing.",
        )
    return ents


def check_ai_adapt_allowed(db: Session, user: User) -> dict:
    ents = get_entitlements(db, user)
    usage = get_usage(db, user)
    limit = int(ents.get("ai_adapts_per_month", 0))
    if usage["ai_adapts_month"] >= limit:
        raise HTTPException(
            403,
            f"Has usado todas las mejoras con IA de este mes ({limit}). "
            "En Planes puedes ampliar el cupo (Esencial 20, Profesional 100, Carrera Max ilimitado).",
        )
    return ents


def check_cv_create_allowed(db: Session, user: User, current_count: int) -> dict:
    ents = get_entitlements(db, user)
    limit = int(ents.get("max_cvs", 1))
    if current_count >= limit:
        raise HTTPException(
            403,
            f"Has llegado al máximo de currículums de tu plan ({limit}). "
            "Puedes sustituir uno que ya tengas (subir PDF encima) o ampliar en Planes.",
        )
    return ents


def check_application_allowed(db: Session, user: User, current_count: int) -> dict:
    ents = get_entitlements(db, user)
    limit = int(ents.get("max_applications", 20))
    if limit >= 0 and current_count >= limit:
        raise HTTPException(
            403,
            f"Límite de candidaturas del plan ({limit}). Mejora tu plan en /pricing.",
        )
    return ents


def source_allowed_for_user(db: Session, user: User, source) -> bool:
    """source: JobSource ORM."""
    if user.is_admin:
        return True
    ents = get_entitlements(db, user)
    if ents.get("all_sources"):
        return True
    rank = user_plan_rank(db, user)
    if getattr(source, "tier_min", 0) > rank:
        return False
    regions = set(ents.get("regions") or ["es"])
    region = getattr(source, "region", "intl") or "intl"
    if region not in regions and not (ents.get("intl_sources") and region in ("intl", "latam")):
        return False
    return True
