from __future__ import annotations

from datetime import UTC, datetime, timedelta

import pytest
from fastapi import HTTPException
from sqlalchemy import create_engine, event, func, select
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool

from app.models import Base
from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan, UserEntitlement, UserEntitlementLog
from app.models.user import User
from app.services.chat_service import ChatService
from app.services.entitlement_service import EntitlementService, entitlement_dict
from app.services.rag_service import RagResult, RagService
from app.services.topic_session_service import TopicSessionService


def _db() -> Session:
    engine = create_engine("sqlite:///:memory:", connect_args={"check_same_thread": False}, poolclass=StaticPool)
    Base.metadata.create_all(engine)
    return Session(engine)


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


def _seed_user_session(db: Session) -> tuple[User, ChatSession]:
    user = User(id=1, phone="13800000001", name="测试用户", daily_chat_limit=100, daily_chat_used=0)
    session = ChatSession(id=1, user_id=1, title="新聊天", message_count=0, last_message_at=_now(), is_deleted=0)
    db.add_all([user, session])
    db.commit()
    return user, session


def test_default_entitlement_uses_basic_plan_when_user_has_no_assignment():
    with _db() as db:
        user, _session = _seed_user_session(db)
        db.add(
            EntitlementPlan(
                id=10,
                name="大本营基础版",
                plan_type="basic",
                description="基础知识问答权益",
                validity_days=180,
                monthly_topic_limit=30,
                status=1,
                sort_order=10,
            )
        )
        db.commit()

        view = EntitlementService.active_entitlement(db, user, monthly_topic_used=3)

        assert view.plan_id == 10
        assert view.name == "大本营基础版"
        assert view.description == "基础知识问答权益"
        assert view.validity_days == 180
        assert view.source == "default"
        assert view.monthly_topic_remaining == 27
        assert entitlement_dict(view)["description"] == "基础知识问答权益"
        assert entitlement_dict(view)["validityDays"] == 180


def test_legacy_teacher_plan_is_not_exposed_or_used_as_default():
    with _db() as db:
        user, _session = _seed_user_session(db)
        db.add(
            EntitlementPlan(
                id=10,
                name="旧老师工作版",
                plan_type="teacher",
                monthly_topic_limit=None,
                status=1,
                sort_order=1,
            )
        )
        db.commit()

        plans = EntitlementService.list_plans(db, include_disabled=True)
        view = EntitlementService.active_entitlement(db, user)

        assert plans == []
        assert view.plan_type == "legacy"


def test_assign_user_plan_replaces_previous_active_plan():
    with _db() as db:
        user, _session = _seed_user_session(db)
        db.add_all(
            [
                EntitlementPlan(id=10, name="基础版", plan_type="basic", monthly_topic_limit=30, status=1, sort_order=10),
                EntitlementPlan(id=20, name="深度陪伴版", plan_type="deep", monthly_topic_limit=90, status=1, sort_order=20),
            ]
        )
        db.commit()

        first = EntitlementService.assign_user_plan(db, user=user, plan_id=10, operated_by=99)
        second = EntitlementService.assign_user_plan(db, user=user, plan_id=20, operated_by=99)
        db.commit()

        db.refresh(first)
        db.refresh(second)
        view = EntitlementService.active_entitlement(db, user, monthly_topic_used=4)

        assert first.status == "replaced"
        assert second.status == "active"
        assert view.plan_id == 20
        assert view.source == "assigned"
        assert view.monthly_topic_remaining == 86


def test_expired_entitlement_falls_back_and_keeps_previous_plan_context():
    with _db() as db:
        user, _session = _seed_user_session(db)
        db.add_all(
            [
                EntitlementPlan(id=10, name="基础版", plan_type="basic", monthly_topic_limit=30, status=1, sort_order=10),
                EntitlementPlan(id=20, name="深度陪伴版", plan_type="deep", monthly_topic_limit=90, status=1, sort_order=20),
                UserEntitlement(
                    id=100,
                    user_id=user.id,
                    plan_id=20,
                    status="active",
                    effective_at=_now() - timedelta(days=40),
                    expired_at=_now() - timedelta(days=1),
                ),
            ]
        )
        db.commit()

        view = EntitlementService.active_entitlement(db, user)
        expired_count = EntitlementService.expire_due_entitlements(db)
        db.commit()

        assert view.plan_id == 10
        assert view.lifecycle_status == "expired_fallback"
        assert view.previous_plan_name == "深度陪伴版"
        assert view.previous_expired_at is not None
        assert expired_count == 1
        assert db.get(UserEntitlement, 100).status == "expired"
        assert db.scalar(select(UserEntitlementLog).where(UserEntitlementLog.action == "expire")) is not None


def test_renew_user_plan_extends_from_current_expiry_and_is_idempotent():
    with _db() as db:
        user, _session = _seed_user_session(db)
        current_expiry = _now() + timedelta(days=5)
        db.add(EntitlementPlan(id=20, name="深度陪伴版", plan_type="deep", monthly_topic_limit=90, status=1))
        db.add(
            UserEntitlement(
                id=100,
                user_id=user.id,
                plan_id=20,
                status="active",
                effective_at=_now() - timedelta(days=20),
                expired_at=current_expiry,
            )
        )
        db.commit()

        renewed = EntitlementService.renew_user_plan(
            db,
            user=user,
            extension_days=30,
            request_key="renew-test-key:1",
            operated_by=99,
            remark="测试续期",
        )
        db.commit()
        duplicate = EntitlementService.renew_user_plan(
            db,
            user=user,
            extension_days=30,
            request_key="renew-test-key:1",
            operated_by=99,
            remark="重复请求",
        )
        db.commit()

        assert renewed.id == duplicate.id
        assert renewed.expired_at == current_expiry + timedelta(days=30)
        assert db.get(UserEntitlement, 100).status == "replaced"
        assert db.scalar(select(func.count(UserEntitlementLog.id)).where(UserEntitlementLog.action == "renew")) == 1


def test_renew_expired_user_plan_restores_same_plan_from_now():
    with _db() as db:
        user, _session = _seed_user_session(db)
        db.add(EntitlementPlan(id=20, name="深度陪伴版", plan_type="deep", monthly_topic_limit=90, status=1))
        db.add(
            UserEntitlement(
                id=100,
                user_id=user.id,
                plan_id=20,
                status="expired",
                effective_at=_now() - timedelta(days=40),
                expired_at=_now() - timedelta(days=1),
            )
        )
        db.commit()

        renewed = EntitlementService.renew_user_plan(
            db,
            user=user,
            extension_days=30,
            request_key="restore-test-key:1",
            operated_by=99,
        )
        db.commit()
        view = EntitlementService.active_entitlement(db, user)

        assert renewed.expired_at is not None
        assert timedelta(days=29) < renewed.expired_at - _now() <= timedelta(days=30)
        assert view.plan_id == 20
        assert view.source == "assigned"


def test_monthly_topic_quota_blocks_new_topic_but_allows_existing_topic():
    with _db() as db:
        user, session = _seed_user_session(db)
        db.add(EntitlementPlan(id=10, name="限额版", plan_type="basic", monthly_topic_limit=1, status=1, sort_order=10))
        db.add(
            TopicSession(
                id=100,
                user_id=user.id,
                chat_session_id=99,
                title="旧主题",
                core_question="旧主题",
                status="active",
                quota_deducted=1,
                started_at=_now(),
                created_at=_now(),
                updated_at=_now(),
            )
        )
        db.commit()
        entitlement = EntitlementService.active_entitlement(db, user, monthly_topic_used=TopicSessionService.monthly_used_count(db, user.id))

        with pytest.raises(HTTPException) as exc:
            ChatService._ensure_topic_quota(db, user, session, entitlement)

        assert exc.value.status_code == 403

        TopicSessionService.get_or_create_active(
            db,
            user=user,
            session=session,
            question="当前主题",
            deduct_quota=True,
        )
        db.flush()

        ChatService._ensure_topic_quota(db, user, session, entitlement)


def test_formal_chat_passes_topic_and_product_context_to_rag(monkeypatch):
    with _db() as db:
        message_sequence = iter(range(1000, 1010))

        def assign_sqlite_message_id(_session, _flush_context, _instances):
            for instance in _session.new:
                if isinstance(instance, ChatMessage) and instance.id is None:
                    instance.id = next(message_sequence)

        event.listen(db, "before_flush", assign_sqlite_message_id)
        user, session = _seed_user_session(db)
        db.add(
            EntitlementPlan(
                id=10,
                name="基础陪伴版",
                plan_type="basic",
                monthly_topic_limit=30,
                allow_help_card=1,
                allow_share_draft=0,
                status=1,
            )
        )
        db.commit()
        captured = {}

        def fake_build_result(_db, _user, question, **kwargs):
            captured.update(kwargs)
            return RagResult(
                question=question,
                knowledge_scopes=[],
                chunks=[],
                prompt="测试 prompt",
                allow_general_knowledge=True,
            )

        monkeypatch.setattr(RagService, "build_result", fake_build_result)

        ChatService.create_answer(db, user, session.id, "我想继续这个问题")

        assert "[当前对话主题]" in captured["topic_context"]
        assert "我想继续这个问题" in captured["topic_context"]
        assert "[当前产品权益]" in captured["product_context"]
        assert "班级分享稿：当前权益不可生成" in captured["product_context"]


def test_new_topic_resets_previous_topic_rolling_summary():
    with _db() as db:
        user, session = _seed_user_session(db)
        session.summary = "上一个主题的滚动摘要"
        session.summary_up_to_message_id = 88
        db.add(
            TopicSession(
                id=90,
                user_id=user.id,
                chat_session_id=session.id,
                title="已完成主题",
                core_question="上一个问题",
                status="completed",
            )
        )
        db.commit()

        topic = TopicSessionService.get_or_create_active(
            db,
            user=user,
            session=session,
            question="这是一个新主题",
            deduct_quota=True,
        )

        assert topic.id != 90
        assert topic.status == "active"
        assert session.summary is None
        assert session.summary_up_to_message_id is None
