from __future__ import annotations

from datetime import UTC, datetime, timedelta

from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from sqlalchemy.pool import StaticPool

from app.models import Base
from app.models.ai_config import SystemConfig
from app.models.chat import ChatSession, TopicSession
from app.models.entitlement import EntitlementPlan, UserEntitlement
from app.models.growth import PeriodicReport, TopicSummary, UserGrowthProfile
from app.models.user import User
from app.services.periodic_report_service import PeriodicReportService, periodic_report_dict
from app.services.periodic_report_worker import PeriodicReportWorker, scheduled_period
from app.services.model_service import ModelCompletion
from app.services.tracked_generation_service import TrackedGenerationService


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 test_generate_periodic_report_from_topic_summaries():
    with _db() as db:
        now = _now()
        db.add(SystemConfig(config_key="mock_model_enabled", config_value="true"))
        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=2, last_message_at=now, is_deleted=0)
        topic = TopicSession(id=1, user_id=1, chat_session_id=1, title="表达障碍", core_question="我不敢表达", status="completed")
        summary = TopicSummary(
            id=1,
            user_id=1,
            topic_session_id=1,
            summary="本周反复看到表达时身体发紧。",
            emotions="害怕、紧张",
            body_feelings="喉咙紧、胸口堵",
            recommended_homework="表达障碍练习",
            next_observation="先观察身体反应",
            generated_at=now - timedelta(days=1),
        )
        db.add_all([user, session, topic, summary, UserGrowthProfile(user_id=1, profile_text="用户常在表达前身体发紧。")])
        db.commit()

        report = PeriodicReportService.generate_for_user(db, user=user, report_type="weekly", period_start=now - timedelta(days=7), period_end=now)

        assert report.status == "success"
        assert report.schema_version == 2
        assert report.content
        assert "长期成长档案" not in report.content
        assert "推荐功课：" not in report.content
        assert "情绪：害怕" not in report.content
        data = periodic_report_dict(report)
        assert data["sourceSummaryIds"] == [1]
        assert data["sourceTopicIds"] == [1]
        assert db.query(PeriodicReport).filter_by(user_id=1).count() == 1


def test_generate_empty_periodic_report_when_no_summaries():
    with _db() as db:
        user = User(id=1, phone="13800000001", name="学员", daily_chat_limit=100, daily_chat_used=0)
        db.add(user)
        db.commit()

        report = PeriodicReportService.generate_for_user(db, user=user, report_type="monthly")

        assert report.status == "empty"
        assert "还没有可用于生成报告的主题沉淀" in report.content
        assert periodic_report_dict(report)["sourceSummaryIds"] == []


def test_periodic_report_strips_model_reasoning_before_saving(monkeypatch):
    with _db() as db:
        now = _now()
        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=1, last_message_at=now, is_deleted=0)
        topic = TopicSession(id=1, user_id=1, chat_session_id=1, title="当下", core_question="我想先看当下", status="completed")
        summary = TopicSummary(
            id=1,
            user_id=1,
            topic_session_id=1,
            summary="最近谈到想先看当下。",
            generated_at=now - timedelta(days=1),
        )
        db.add_all([user, session, topic, summary])
        db.commit()
        monkeypatch.setattr(
            TrackedGenerationService,
            "generate",
            staticmethod(
                lambda *_args, **_kwargs: ModelCompletion(
                    answer="<think>内部分析与旧画像标签</think>\n## 本周期谈到的主题\n\n最近谈到想先看当下。",
                    model_id=1,
                    model_name="test-model",
                    input_token=1,
                    output_token=1,
                )
            ),
        )

        report = PeriodicReportService.generate_for_user(
            db,
            user=user,
            report_type="weekly",
            period_start=now - timedelta(days=7),
            period_end=now,
        )

        assert report.content.startswith("## 本周期谈到的主题")
        assert "内部分析" not in report.content
        assert "<think>" not in report.content


def test_legacy_periodic_reports_are_hidden_until_regenerated():
    with _db() as db:
        now = _now()
        user = User(id=1, phone="13800000001", name="学员", daily_chat_limit=100, daily_chat_used=0)
        legacy = PeriodicReport(
            user_id=1,
            schema_version=1,
            report_type="weekly",
            period_start=now - timedelta(days=7),
            period_end=now,
            title="旧版成长报告",
            content="常见情绪、身体模式、有效功课和近期变化",
            status="success",
        )
        db.add_all([user, legacy])
        db.commit()

        assert PeriodicReportService.list_user_reports(db, user_id=1) == []


def test_async_report_job_is_durable_and_idempotent():
    with _db() as db:
        now = _now()
        db.add(SystemConfig(config_key="mock_model_enabled", config_value="true"))
        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=2, last_message_at=now, is_deleted=0)
        topic = TopicSession(id=1, user_id=1, chat_session_id=1, title="本周主题", core_question="我该怎么观察", status="completed")
        summary = TopicSummary(
            id=1,
            user_id=1,
            topic_session_id=1,
            summary="本周看见了身体紧张。",
            generated_at=now - timedelta(days=1),
        )
        db.add_all([user, session, topic, summary])
        db.commit()

        period_start = now - timedelta(days=7)
        report = PeriodicReportService.enqueue_for_user(
            db,
            user=user,
            report_type="weekly",
            period_start=period_start,
            period_end=now,
            generated_by="admin:1",
        )
        db.commit()
        duplicate = PeriodicReportService.enqueue_for_user(
            db,
            user=user,
            report_type="weekly",
            period_start=period_start,
            period_end=now,
            generated_by="admin:1",
        )
        db.commit()

        assert duplicate.id == report.id
        assert db.query(PeriodicReport).count() == 1
        assert duplicate.status == "pending"

        report_id = PeriodicReportWorker.claim_next(db, worker_id="test-worker", now=_now() + timedelta(seconds=1))
        assert report_id == report.id
        running = db.get(PeriodicReport, report.id)
        assert running is not None
        assert running.status == "running"
        assert running.attempt_count == 1

        completed = PeriodicReportWorker.execute_claimed(db, report_id=report.id, worker_id="test-worker")
        assert completed is not None
        assert completed.status == "success"
        assert completed.finished_at is not None
        assert completed.locked_by is None


def test_failed_async_report_is_retried(monkeypatch):
    with _db() as db:
        now = _now()
        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=2, last_message_at=now, is_deleted=0)
        topic = TopicSession(id=1, user_id=1, chat_session_id=1, title="本周主题", core_question="我该怎么观察", status="completed")
        summary = TopicSummary(
            id=1,
            user_id=1,
            topic_session_id=1,
            summary="本周看见了身体紧张。",
            generated_at=now - timedelta(days=1),
        )
        db.add_all([user, session, topic, summary])
        db.commit()
        report = PeriodicReportService.enqueue_for_user(
            db,
            user=user,
            report_type="weekly",
            period_start=now - timedelta(days=7),
            period_end=now,
        )
        db.commit()
        monkeypatch.setattr(
            TrackedGenerationService,
            "generate",
            staticmethod(lambda *_args, **_kwargs: (_ for _ in ()).throw(RuntimeError("模型暂时不可用"))),
        )

        report_id = PeriodicReportWorker.claim_next(db, worker_id="retry-worker", now=_now() + timedelta(seconds=1))
        result = PeriodicReportWorker.execute_claimed(db, report_id=report_id, worker_id="retry-worker")

        assert result is not None
        assert result.status == "pending"
        assert result.attempt_count == 1
        assert result.next_run_at is not None
        assert "模型暂时不可用" in (result.error_message or "")


def test_schedule_enqueues_only_users_with_enabled_report_entitlement():
    with _db() as db:
        now = datetime(2026, 8, 2, 19, 0, 0)  # 上海时间 2026-08-03 周一 03:00
        plan = EntitlementPlan(
            id=1,
            name="深度陪伴版",
            plan_type="deep",
            enable_periodic_reports=1,
            status=1,
        )
        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=2, last_message_at=now, is_deleted=0)
        topic = TopicSession(id=1, user_id=1, chat_session_id=1, title="本周主题", core_question="本周问题", status="completed")
        summary = TopicSummary(
            id=1,
            user_id=1,
            topic_session_id=1,
            summary="本周主题沉淀",
            generated_at=datetime(2026, 8, 1, 8, 0, 0),
        )
        entitlement = UserEntitlement(id=1, user_id=1, plan_id=1, status="active")
        db.add_all([plan, user, session, topic, summary, entitlement])
        db.commit()

        first = PeriodicReportWorker.enqueue_due_schedules(db, now_utc=now)
        db.commit()
        second = PeriodicReportWorker.enqueue_due_schedules(db, now_utc=now)
        db.commit()

        report = db.scalar(select(PeriodicReport).where(PeriodicReport.report_type == "weekly"))
        assert first["weekly"] == 1
        assert second == {}
        assert report is not None
        assert report.status == "pending"
        assert report.generated_by == "schedule:weekly"


def test_scheduled_period_uses_shanghai_calendar_boundaries():
    weekly = scheduled_period("weekly", datetime(2026, 8, 2, 19, 0, 0))
    monthly = scheduled_period("monthly", datetime(2026, 7, 31, 19, 0, 0))
    manual_weekly_a = PeriodicReportService.default_period("weekly", datetime(2026, 7, 29, 2, 0, 0))
    manual_weekly_b = PeriodicReportService.default_period("weekly", datetime(2026, 7, 31, 2, 0, 0))

    assert weekly == (datetime(2026, 7, 26, 16, 0, 0), datetime(2026, 8, 2, 16, 0, 0))
    assert monthly == (datetime(2026, 6, 30, 16, 0, 0), datetime(2026, 7, 31, 16, 0, 0))
    assert manual_weekly_a == manual_weekly_b


def test_stale_running_job_is_recovered_after_restart():
    with _db() as db:
        now = _now()
        user = User(id=1, phone="13800000001", name="学员", daily_chat_limit=100, daily_chat_used=0)
        db.add(user)
        db.commit()
        report = PeriodicReportService.enqueue_for_user(
            db,
            user=user,
            report_type="weekly",
            period_start=now - timedelta(days=7),
            period_end=now,
        )
        report.status = "running"
        report.attempt_count = 1
        report.locked_by = "stopped-worker"
        report.locked_at = now - timedelta(minutes=31)
        db.commit()

        recovered = PeriodicReportWorker.recover_stale_jobs(db, now=now)
        db.commit()
        db.refresh(report)

        assert recovered == 1
        assert report.status == "pending"
        assert report.locked_by is None
        assert report.next_run_at == now
        assert "自动恢复" in (report.error_message or "")
