from __future__ import annotations

import json
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta

from fastapi import HTTPException, status
from sqlalchemy import select
from sqlalchemy.orm import Session

from app.models.entitlement import EntitlementPlan, UserEntitlement, UserEntitlementLog
from app.models.user import User


DEFAULT_PLAN_TYPE = "basic"


@dataclass(frozen=True)
class EntitlementView:
    plan_id: int | None
    name: str
    plan_type: str
    monthly_topic_limit: int | None
    monthly_topic_used: int
    enable_growth_profile: bool
    enable_periodic_reports: bool
    allow_help_card: bool
    allow_share_draft: bool
    deduct_quota: bool
    effective_at: datetime | None = None
    expired_at: datetime | None = None
    source: str = "legacy"

    @property
    def monthly_topic_remaining(self) -> int | None:
        if self.monthly_topic_limit is None:
            return None
        return max(0, self.monthly_topic_limit - self.monthly_topic_used)


class EntitlementService:
    @staticmethod
    def list_plans(db: Session, *, include_disabled: bool = False) -> list[EntitlementPlan]:
        query = select(EntitlementPlan)
        if not include_disabled:
            query = query.where(EntitlementPlan.status == 1)
        return list(db.scalars(query.order_by(EntitlementPlan.sort_order.asc(), EntitlementPlan.id.asc())))

    @staticmethod
    def default_plan(db: Session) -> EntitlementPlan | None:
        plan = db.scalar(
            select(EntitlementPlan)
            .where(EntitlementPlan.plan_type == DEFAULT_PLAN_TYPE, EntitlementPlan.status == 1)
            .order_by(EntitlementPlan.sort_order.asc(), EntitlementPlan.id.asc())
            .limit(1)
        )
        if plan is not None:
            return plan
        return db.scalar(
            select(EntitlementPlan)
            .where(EntitlementPlan.status == 1)
            .order_by(EntitlementPlan.sort_order.asc(), EntitlementPlan.id.asc())
            .limit(1)
        )

    @staticmethod
    def active_entitlement(db: Session, user: User, *, monthly_topic_used: int = 0) -> EntitlementView:
        now = _now()
        row = db.execute(
            select(UserEntitlement, EntitlementPlan)
            .join(EntitlementPlan, EntitlementPlan.id == UserEntitlement.plan_id)
            .where(
                UserEntitlement.user_id == user.id,
                UserEntitlement.status == "active",
                EntitlementPlan.status == 1,
            )
            .where((UserEntitlement.effective_at.is_(None)) | (UserEntitlement.effective_at <= now))
            .where((UserEntitlement.expired_at.is_(None)) | (UserEntitlement.expired_at >= now))
            .order_by(UserEntitlement.created_at.desc(), UserEntitlement.id.desc())
            .limit(1)
        ).first()
        if row:
            entitlement, plan = row
            return view_from_plan(plan, monthly_topic_used=monthly_topic_used, entitlement=entitlement, source="assigned")

        plan = EntitlementService.default_plan(db)
        if plan is not None:
            return view_from_plan(plan, monthly_topic_used=monthly_topic_used, entitlement=None, source="default")

        return EntitlementView(
            plan_id=None,
            name="旧版每日额度",
            plan_type="legacy",
            monthly_topic_limit=None,
            monthly_topic_used=monthly_topic_used,
            enable_growth_profile=False,
            enable_periodic_reports=False,
            allow_help_card=True,
            allow_share_draft=True,
            deduct_quota=True,
            source="legacy",
        )

    @staticmethod
    def assign_user_plan(
        db: Session,
        *,
        user: User,
        plan_id: int,
        operated_by: int | None,
        effective_at: datetime | None = None,
        expired_at: datetime | None = None,
        remark: str | None = None,
    ) -> UserEntitlement:
        plan = db.get(EntitlementPlan, plan_id)
        if plan is None or plan.status != 1:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="权益版本不存在或已停用")
        now = _now()
        if effective_at is not None:
            effective_at = effective_at.replace(tzinfo=None)
        if expired_at is None and plan.validity_days:
            start = effective_at or now
            expired_at = start + timedelta(days=plan.validity_days)
        elif expired_at is not None:
            expired_at = expired_at.replace(tzinfo=None)
        if expired_at is not None and effective_at is not None and expired_at < effective_at:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="权益到期时间不能早于生效时间")

        current = db.scalar(
            select(UserEntitlement)
            .where(UserEntitlement.user_id == user.id, UserEntitlement.status == "active")
            .order_by(UserEntitlement.created_at.desc(), UserEntitlement.id.desc())
            .limit(1)
        )
        from_plan_id = current.plan_id if current else None
        if current is not None:
            current.status = "replaced"
            db.add(current)

        entitlement = UserEntitlement(
            user_id=user.id,
            plan_id=plan.id,
            status="active",
            effective_at=effective_at,
            expired_at=expired_at,
            assigned_by=operated_by,
            remark=remark,
        )
        db.add(entitlement)
        db.flush()
        db.add(
            UserEntitlementLog(
                user_id=user.id,
                entitlement_id=entitlement.id,
                from_plan_id=from_plan_id,
                to_plan_id=plan.id,
                action="assign",
                detail_json=json.dumps(
                    {
                        "effectiveAt": effective_at.isoformat() if effective_at else None,
                        "expiredAt": expired_at.isoformat() if expired_at else None,
                        "remark": remark,
                    },
                    ensure_ascii=False,
                ),
                operated_by=operated_by,
                created_at=now,
            )
        )
        return entitlement


def plan_dict(plan: EntitlementPlan) -> dict:
    return {
        "id": plan.id,
        "name": plan.name,
        "planType": plan.plan_type,
        "description": plan.description,
        "validityDays": plan.validity_days,
        "monthlyTopicLimit": plan.monthly_topic_limit,
        "enableGrowthProfile": bool(plan.enable_growth_profile),
        "enablePeriodicReports": bool(plan.enable_periodic_reports),
        "allowHelpCard": bool(plan.allow_help_card),
        "allowShareDraft": bool(plan.allow_share_draft),
        "deductQuota": bool(plan.deduct_quota),
        "status": plan.status,
        "sortOrder": plan.sort_order,
        "createdAt": plan.created_at,
        "updatedAt": plan.updated_at,
    }


def entitlement_dict(view: EntitlementView) -> dict:
    return {
        "planId": view.plan_id,
        "name": view.name,
        "planType": view.plan_type,
        "monthlyTopicLimit": view.monthly_topic_limit,
        "monthlyTopicUsed": view.monthly_topic_used,
        "monthlyTopicRemaining": view.monthly_topic_remaining,
        "enableGrowthProfile": view.enable_growth_profile,
        "enablePeriodicReports": view.enable_periodic_reports,
        "allowHelpCard": view.allow_help_card,
        "allowShareDraft": view.allow_share_draft,
        "deductQuota": view.deduct_quota,
        "effectiveAt": view.effective_at,
        "expiredAt": view.expired_at,
        "source": view.source,
    }


def view_from_plan(
    plan: EntitlementPlan,
    *,
    monthly_topic_used: int,
    entitlement: UserEntitlement | None,
    source: str,
) -> EntitlementView:
    return EntitlementView(
        plan_id=plan.id,
        name=plan.name,
        plan_type=plan.plan_type,
        monthly_topic_limit=plan.monthly_topic_limit,
        monthly_topic_used=monthly_topic_used,
        enable_growth_profile=bool(plan.enable_growth_profile),
        enable_periodic_reports=bool(plan.enable_periodic_reports),
        allow_help_card=bool(plan.allow_help_card),
        allow_share_draft=bool(plan.allow_share_draft),
        deduct_quota=bool(plan.deduct_quota),
        effective_at=entitlement.effective_at if entitlement else None,
        expired_at=entitlement.expired_at if entitlement else None,
        source=source,
    )


def _now() -> datetime:
    return datetime.now(UTC).replace(tzinfo=None)
