from __future__ import annotations

import json
import re
from datetime import UTC, datetime
from typing import Any

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

from app.models.chat import ChatMessage, ChatSession, TopicSession
from app.models.growth import GrowthProfileRevision, TopicSummary, UserGrowthProfile
from app.models.user import User
from app.services.entitlement_service import EntitlementService
from app.services.external_errors import ExternalServiceError
from app.services.model_service import ModelClientService
from app.services.topic_session_service import TopicSessionService


class GrowthProfileService:
    @staticmethod
    def finish_active_topic(db: Session, *, user: User, session: ChatSession, force: bool = False) -> dict:
        topic = TopicSessionService.active_for_session(db, user=user, session=session)
        if topic is None:
            raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="当前会话没有进行中的主题")
        summary = GrowthProfileService.generate_topic_summary(db, user=user, topic=topic, force=force)
        topic.status = "completed"
        topic.ended_at = _now()
        topic.recommended_homework = summary.recommended_homework
        db.add(topic)

        entitlement = EntitlementService.active_entitlement(db, user)
        profile = None
        if entitlement.enable_growth_profile:
            profile = GrowthProfileService.update_growth_profile(db, user=user, topic_summary=summary)
        db.commit()
        db.refresh(topic)
        db.refresh(summary)
        if profile is not None:
            db.refresh(profile)
        return {
            "topic": topic_dict(topic),
            "summary": topic_summary_dict(summary),
            "profile": growth_profile_dict(profile) if profile is not None else None,
            "growthProfileEnabled": entitlement.enable_growth_profile,
        }

    @staticmethod
    def generate_topic_summary(db: Session, *, user: User, topic: TopicSession, force: bool = False) -> TopicSummary:
        existing = db.scalar(select(TopicSummary).where(TopicSummary.topic_session_id == topic.id))
        if existing is not None and not force:
            return existing
        messages = list(
            db.scalars(
                select(ChatMessage)
                .where(ChatMessage.topic_session_id == topic.id, ChatMessage.user_id == user.id)
                .order_by(ChatMessage.created_at.asc(), ChatMessage.id.asc())
            )
        )
        if not messages:
            raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="当前主题还没有可沉淀的对话内容")
        conversation = _messages_text(messages)
        prompt = _topic_summary_prompt(topic, conversation)
        model_name = _enabled_model_name(db)
        try:
            raw = ModelClientService.generate_text_or_raise(db, prompt)
            parsed = _parse_summary_json(raw)
            data = parsed if parsed and "summary" in parsed else _fallback_summary(topic, conversation, raw)
            status_value = "success"
            error_message = None
        except ExternalServiceError as exc:
            data = _fallback_summary(topic, conversation, "")
            status_value = "fallback"
            error_message = str(exc)

        if existing is None:
            existing = TopicSummary(topic_session_id=topic.id, user_id=user.id)
        _apply_summary(existing, data)
        existing.model_name = model_name
        existing.status = status_value
        existing.error_message = error_message
        existing.generated_at = _now()
        db.add(existing)
        db.flush()
        return existing

    @staticmethod
    def update_growth_profile(db: Session, *, user: User, topic_summary: TopicSummary) -> UserGrowthProfile:
        profile = db.scalar(select(UserGrowthProfile).where(UserGrowthProfile.user_id == user.id))
        before = growth_profile_dict(profile) if profile is not None else None
        if profile is None:
            profile = UserGrowthProfile(user_id=user.id, profile_text="")
            db.add(profile)
            db.flush()

        prompt = _growth_profile_prompt(profile, topic_summary)
        try:
            raw = ModelClientService.generate_text_or_raise(db, prompt)
            parsed = _parse_summary_json(raw)
            data = parsed if parsed and "profileText" in parsed else _fallback_profile(profile, topic_summary, raw)
        except ExternalServiceError:
            data = _fallback_profile(profile, topic_summary, "")

        _apply_profile(profile, data)
        profile.last_topic_summary_id = topic_summary.id
        profile.updated_by = "system"
        db.add(profile)
        db.flush()
        after = growth_profile_dict(profile)
        db.add(
            GrowthProfileRevision(
                user_id=user.id,
                profile_id=profile.id,
                topic_summary_id=topic_summary.id,
                before_json=json.dumps(before, ensure_ascii=False, default=str) if before else None,
                after_json=json.dumps(after, ensure_ascii=False, default=str),
                reason="topic_summary",
            )
        )
        return profile

    @staticmethod
    def get_growth_profile(db: Session, user_id: int) -> UserGrowthProfile | None:
        return db.scalar(select(UserGrowthProfile).where(UserGrowthProfile.user_id == user_id))

    @staticmethod
    def recent_topic_summaries(db: Session, user_id: int, *, limit: int = 10) -> list[TopicSummary]:
        return list(
            db.scalars(
                select(TopicSummary)
                .where(TopicSummary.user_id == user_id)
                .order_by(TopicSummary.generated_at.desc(), TopicSummary.id.desc())
                .limit(limit)
            )
        )

    @staticmethod
    def prompt_context(db: Session, user: User) -> str | None:
        profile = GrowthProfileService.get_growth_profile(db, user.id)
        if profile is None or not profile.profile_text.strip():
            return None
        return (
            "[长期成长档案]\n"
            "以下是用户跨主题沉淀出的简要成长档案，只能用于理解用户长期模式和延续陪伴，"
            "不能替代本轮可靠知识，也不能臆造课程内容。\n"
            f"{_limit(profile.profile_text, 1800)}"
        )


def topic_dict(topic: TopicSession) -> dict:
    return {
        "id": topic.id,
        "userId": topic.user_id,
        "chatSessionId": topic.chat_session_id,
        "title": topic.title,
        "coreQuestion": topic.core_question,
        "status": topic.status,
        "messageCount": topic.message_count,
        "tokenInput": topic.token_input,
        "tokenOutput": topic.token_output,
        "quotaDeducted": bool(topic.quota_deducted),
        "recommendedHomework": topic.recommended_homework,
        "startedAt": topic.started_at,
        "endedAt": topic.ended_at,
        "createdAt": topic.created_at,
        "updatedAt": topic.updated_at,
    }


def topic_summary_dict(summary: TopicSummary) -> dict:
    return {
        "id": summary.id,
        "topicSessionId": summary.topic_session_id,
        "userId": summary.user_id,
        "summary": summary.summary,
        "mainEvents": summary.main_events,
        "emotions": summary.emotions,
        "bodyFeelings": summary.body_feelings,
        "beliefs": summary.beliefs,
        "recommendedHomework": summary.recommended_homework,
        "insights": summary.insights,
        "nextObservation": summary.next_observation,
        "modelName": summary.model_name,
        "status": summary.status,
        "errorMessage": summary.error_message,
        "generatedAt": summary.generated_at,
    }


def growth_profile_dict(profile: UserGrowthProfile | None) -> dict | None:
    if profile is None:
        return None
    return {
        "id": profile.id,
        "userId": profile.user_id,
        "profileText": profile.profile_text,
        "recurringTopics": profile.recurring_topics,
        "commonEmotions": profile.common_emotions,
        "bodyPatterns": profile.body_patterns,
        "relationPatterns": profile.relation_patterns,
        "homeworkDone": profile.homework_done,
        "effectiveHomework": profile.effective_homework,
        "recentProgress": profile.recent_progress,
        "lastTopicSummaryId": profile.last_topic_summary_id,
        "updatedBy": profile.updated_by,
        "createdAt": profile.created_at,
        "updatedAt": profile.updated_at,
    }


def _apply_summary(summary: TopicSummary, data: dict[str, Any]) -> None:
    summary.summary = _field(data, "summary", "本主题已沉淀。", 4000)
    summary.main_events = _field(data, "mainEvents", "", 2000) or None
    summary.emotions = _field(data, "emotions", "", 1000) or None
    summary.body_feelings = _field(data, "bodyFeelings", "", 1000) or None
    summary.beliefs = _field(data, "beliefs", "", 1000) or None
    summary.recommended_homework = _field(data, "recommendedHomework", "", 1500) or None
    summary.insights = _field(data, "insights", "", 1500) or None
    summary.next_observation = _field(data, "nextObservation", "", 1500) or None


def _apply_profile(profile: UserGrowthProfile, data: dict[str, Any]) -> None:
    profile.profile_text = _field(data, "profileText", profile.profile_text or "暂无成长档案。", 5000)
    profile.recurring_topics = _field(data, "recurringTopics", "", 2000) or profile.recurring_topics
    profile.common_emotions = _field(data, "commonEmotions", "", 1500) or profile.common_emotions
    profile.body_patterns = _field(data, "bodyPatterns", "", 1500) or profile.body_patterns
    profile.relation_patterns = _field(data, "relationPatterns", "", 1500) or profile.relation_patterns
    profile.homework_done = _field(data, "homeworkDone", "", 1500) or profile.homework_done
    profile.effective_homework = _field(data, "effectiveHomework", "", 1500) or profile.effective_homework
    profile.recent_progress = _field(data, "recentProgress", "", 1500) or profile.recent_progress


def _topic_summary_prompt(topic: TopicSession, conversation: str) -> str:
    return (
        "你是大本营千问千答的主题沉淀助手。请把一个主题会话总结为结构化 JSON，"
        "只依据对话内容，不要扩展课程知识，不要评价用户人格。输出字段："
        "summary, mainEvents, emotions, bodyFeelings, beliefs, recommendedHomework, insights, nextObservation。"
        "内容要偏“回到当下、回到自身、觉察情绪和感受、如是释放”，不要给很多术层面的建议。\n\n"
        f"主题标题：{topic.title}\n核心问题：{topic.core_question}\n\n对话：\n{_limit(conversation, 12000)}"
    )


def _growth_profile_prompt(profile: UserGrowthProfile, summary: TopicSummary) -> str:
    current = profile.profile_text or "暂无"
    return (
        "你是大本营千问千答的长期成长档案整理助手。请根据旧档案和最新主题摘要，"
        "滚动更新一份结构化 JSON。只保留稳定模式和最近进展，避免堆叠流水账。"
        "输出字段：profileText, recurringTopics, commonEmotions, bodyPatterns, relationPatterns, "
        "homeworkDone, effectiveHomework, recentProgress。语气客观、温和，不诊断、不贴标签。\n\n"
        f"旧档案：\n{_limit(current, 5000)}\n\n最新主题摘要：\n{json.dumps(topic_summary_dict(summary), ensure_ascii=False, default=str)}"
    )


def _fallback_summary(topic: TopicSession, conversation: str, raw: str) -> dict[str, str]:
    source = raw.strip() if raw.strip() and not raw.strip().startswith("你是大本营") else conversation
    user_lines = [line for line in source.splitlines() if line.startswith("用户：")]
    assistant_lines = [line for line in source.splitlines() if line.startswith("大本营答疑助手：")]
    first_question = user_lines[0].removeprefix("用户：").strip() if user_lines else topic.core_question
    last_answer = assistant_lines[-1].removeprefix("大本营答疑助手：").strip() if assistant_lines else ""
    return {
        "summary": _limit(f"用户围绕“{first_question}”进行了提问，本主题的主要回答和沉淀为：{last_answer}", 1800),
        "mainEvents": _limit(first_question, 800),
        "emotions": _join_markers(source, ("害怕", "委屈", "愤怒", "抗拒", "焦虑", "担心", "难受")),
        "bodyFeelings": _join_markers(source, ("身体", "胸", "心口", "头", "胃", "麻", "胀", "紧", "堵")),
        "beliefs": _join_markers(source, ("必须", "应该", "不能", "总是", "一定", "不配")),
        "recommendedHomework": _join_markers(source, ("静心", "觉察", "功课", "练习", "释放", "内省")),
        "insights": _limit(last_answer, 1000),
        "nextObservation": "下次继续从当下最明显的身体感受、情绪和自动念头开始观察。",
    }


def _fallback_profile(profile: UserGrowthProfile, summary: TopicSummary, raw: str) -> dict[str, str]:
    previous = profile.profile_text.strip()
    addition = raw.strip() if raw.strip() and not raw.strip().startswith("你是大本营") else summary.summary
    text = "\n\n".join(part for part in [previous, f"最近主题：{addition}"] if part)
    return {
        "profileText": _limit(text or "暂无成长档案。", 4500),
        "recurringTopics": summary.main_events or "",
        "commonEmotions": summary.emotions or "",
        "bodyPatterns": summary.body_feelings or "",
        "relationPatterns": summary.beliefs or "",
        "homeworkDone": summary.recommended_homework or "",
        "effectiveHomework": summary.recommended_homework or "",
        "recentProgress": summary.insights or summary.summary,
    }


def _parse_summary_json(raw: str) -> dict[str, Any] | None:
    text = raw.strip()
    if not text:
        return None
    if text.startswith("```"):
        text = re.sub(r"^```(?:json)?", "", text, flags=re.IGNORECASE).strip()
        text = re.sub(r"```$", "", text).strip()
    match = re.search(r"\{[\s\S]*\}", text)
    if match:
        text = match.group(0)
    try:
        parsed = json.loads(text)
    except json.JSONDecodeError:
        return None
    return parsed if isinstance(parsed, dict) else None


def _messages_text(messages: list[ChatMessage]) -> str:
    labels = {"user": "用户", "assistant": "大本营答疑助手"}
    return "\n".join(f"{labels.get(message.role, message.role)}：{message.content}" for message in messages)


def _field(data: dict[str, Any], key: str, default: str, max_len: int) -> str:
    value = data.get(key, default)
    if isinstance(value, list):
        value = "；".join(str(item) for item in value if str(item).strip())
    if not isinstance(value, str):
        value = str(value)
    return _limit(value.strip(), max_len)


def _join_markers(text: str, markers: tuple[str, ...]) -> str:
    found = [marker for marker in markers if marker in text]
    return "；".join(found)


def _limit(text: str, max_len: int) -> str:
    return text[:max_len].strip()


def _enabled_model_name(db: Session) -> str | None:
    model = ModelClientService._get_enabled_model(db)
    return model.model_name if model is not None else None


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