"""
플로팅 AI 버튼 + 확장(애니메이션) 채팅 모달

요구사항:
- 웹의 고객응대/AI assistant처럼 우하단 플로팅 버튼
- 클릭 시 작은 패널에서 큰 채팅 모달로 확장(애니메이션)
- 탭 전환 없이 현재 화면 컨텍스트(날짜/열번/편성/호차/타임라인/표시신호/역 전후2개 등) 포함
"""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any, Dict, Optional

from PySide6.QtCore import Qt, QPropertyAnimation, QEasingCurve, QRect, QObject, QThread, Signal, QDateTime
from PySide6.QtGui import QColor
from PySide6.QtWidgets import (
    QWidget,
    QPushButton,
    QDialog,
    QVBoxLayout,
    QHBoxLayout,
    QLabel,
    QComboBox,
    QLineEdit,
    QTextEdit,
    QMessageBox,
    QFrame,
    QDialogButtonBox,
)

from app.ai import get_ai_client
from app.ai.maintenance_kb import get_maintenance_kb
from app.ui.dialogs.api_key_dialog import APIKeyDialog


def _make_json_safe(obj):
    """json.dumps에서 안전하도록 dict/list/QColor 등을 변환"""
    if obj is None:
        return None
    if isinstance(obj, dict):
        return {str(k): _make_json_safe(v) for k, v in obj.items()}
    if isinstance(obj, (list, tuple, set)):
        return [_make_json_safe(v) for v in obj]
    if isinstance(obj, QDateTime):
        try:
            return obj.toString("yyyy-MM-dd HH:mm:ss")
        except Exception:
            return str(obj)
    # QColor 등 name()이 있는 객체 처리
    try:
        if hasattr(obj, "name") and callable(obj.name):
            return obj.name()
    except Exception:
        pass
    # Enum 등 value가 있는 경우
    try:
        if hasattr(obj, "value"):
            return getattr(obj, "value")
    except Exception:
        pass
    return obj


class _AIWorker(QObject):
    finished = Signal(str)
    failed = Signal(str)

    def __init__(self, prompt: str):
        super().__init__()
        self.prompt = prompt

    def run(self):
        try:
            client = get_ai_client()
            if not client.is_ready():
                raise RuntimeError("AI가 준비되지 않았습니다. 설정에서 API 키를 입력하세요.")
            resp = client.chat(self.prompt, temperature=0.2)
            self.finished.emit(resp.content or "")
        except Exception as e:
            self.failed.emit(str(e))


class AIFloatingButton(QPushButton):
    """우하단 플로팅 버튼"""

    def __init__(self, parent: QWidget):
        super().__init__("AI", parent)
        self.setFixedSize(56, 56)
        self.setCursor(Qt.PointingHandCursor)
        self.setToolTip("AI Assistant 열기")
        self.setStyleSheet(
            """
            QPushButton {
                background-color: #0078d4;
                color: white;
                border: none;
                border-radius: 28px;
                font-weight: bold;
                font-size: 14px;
            }
            QPushButton:hover { background-color: #1084d8; }
            QPushButton:pressed { background-color: #006cc1; }
            """
        )
        self._is_open = False

    def set_open_state(self, is_open: bool):
        self._is_open = bool(is_open)
        if self._is_open:
            self.setText("×")
            self.setToolTip("AI Assistant 닫기")
            self.setStyleSheet(
                """
                QPushButton {
                    background-color: #2a2a2a;
                    color: #ddd;
                    border: 1px solid #4a4a4a;
                    border-radius: 28px;
                    font-weight: bold;
                    font-size: 18px;
                }
                QPushButton:hover { background-color: #333; }
                QPushButton:pressed { background-color: #222; }
                """
            )
        else:
            self.setText("AI")
            self.setToolTip("AI Assistant 열기")
            self.setStyleSheet(
                """
                QPushButton {
                    background-color: #0078d4;
                    color: white;
                    border: none;
                    border-radius: 28px;
                    font-weight: bold;
                    font-size: 14px;
                }
                QPushButton:hover { background-color: #1084d8; }
                QPushButton:pressed { background-color: #006cc1; }
                """
            )

    def move_to_bottom_right(self, margin: int = 18):
        p = self.parentWidget()
        if not p:
            return
        x = p.width() - self.width() - margin
        y = p.height() - self.height() - margin
        self.move(max(0, x), max(0, y))


class AIChatDialog(QDialog):
    """확장되는 채팅 모달(프레임리스)"""

    def __init__(self, main_window: QWidget, get_panel_ctx_callable):
        super().__init__(main_window)
        self._get_panel_ctx = get_panel_ctx_callable

        self.setWindowFlags(Qt.FramelessWindowHint | Qt.Dialog)
        self.setModal(False)
        self.setAttribute(Qt.WA_TranslucentBackground, True)

        self._busy = False
        self._thread: Optional[QThread] = None
        self._worker: Optional[_AIWorker] = None
        self._last_payload: Optional[Dict[str, Any]] = None

        self._build_ui()
        self.refresh_header()

    def _build_ui(self):
        root = QVBoxLayout(self)
        root.setContentsMargins(0, 0, 0, 0)

        # 실제 카드(둥근 배경)
        self.card = QFrame()
        self.card.setStyleSheet(
            """
            QFrame {
                background: #1f1f1f;
                border: 1px solid #3a3a3a;
                border-radius: 14px;
            }
            QLabel { color: #ddd; }
            QLineEdit, QTextEdit {
                background: #2a2a2a;
                border: 1px solid #4a4a4a;
                border-radius: 8px;
                padding: 8px;
                color: #eee;
            }
            QTextEdit { font-size: 12px; }
            """
        )
        card_layout = QVBoxLayout(self.card)
        card_layout.setContentsMargins(12, 12, 12, 12)
        card_layout.setSpacing(10)

        # 헤더
        header = QHBoxLayout()
        self.lbl_title = QLabel("AI Assistant")
        self.lbl_title.setStyleSheet("font-weight: bold; font-size: 14px; color: #fff;")

        self.cmb_panel = QComboBox()
        self.cmb_panel.addItem("Left Panel", "left")
        self.cmb_panel.addItem("Right Panel", "right")
        self.cmb_panel.currentIndexChanged.connect(self.refresh_header)

        self.lbl_ai_status = QLabel("AI: 미준비")
        self.lbl_ai_status.setStyleSheet("color: #FFAB00;")

        btn_settings = QPushButton("설정")
        btn_settings.setFixedHeight(30)
        btn_settings.clicked.connect(self._open_settings)
        btn_settings.setStyleSheet(
            "QPushButton{background:#2a2a2a;color:#ddd;border:1px solid #4a4a4a;border-radius:8px;padding:6px 10px;} "
            "QPushButton:hover{background:#333;}"
        )

        btn_close = QPushButton("×")
        btn_close.setFixedSize(30, 30)
        btn_close.clicked.connect(self.close)
        btn_close.setStyleSheet(
            "QPushButton{background:#2a2a2a;color:#ddd;border:1px solid #4a4a4a;border-radius:8px;font-size:16px;} "
            "QPushButton:hover{background:#333;}"
        )

        btn_payload = QPushButton("JSON")
        btn_payload.setFixedHeight(30)
        btn_payload.clicked.connect(self.show_last_payload)
        btn_payload.setStyleSheet(
            "QPushButton{background:#2a2a2a;color:#ddd;border:1px solid #4a4a4a;border-radius:8px;padding:6px 10px;} "
            "QPushButton:hover{background:#333;}"
        )

        header.addWidget(self.lbl_title)
        header.addStretch()
        header.addWidget(self.cmb_panel)
        header.addWidget(self.lbl_ai_status)
        header.addWidget(btn_payload)
        header.addWidget(btn_settings)
        header.addWidget(btn_close)
        card_layout.addLayout(header)

        # 기본정보(자동입력 + 수정가능)
        info_row = QHBoxLayout()
        self.in_date = QLineEdit()
        self.in_date.setPlaceholderText("날짜")
        self.in_trainno = QLineEdit()
        self.in_trainno.setPlaceholderText("열번")
        self.in_car = QLineEdit()
        self.in_car.setPlaceholderText("호차")
        self.in_formation = QLineEdit()
        self.in_formation.setPlaceholderText("편성")
        for w in [self.in_date, self.in_trainno, self.in_car, self.in_formation]:
            w.setFixedHeight(32)
        info_row.addWidget(QLabel("날짜"))
        info_row.addWidget(self.in_date)
        info_row.addWidget(QLabel("열번"))
        info_row.addWidget(self.in_trainno)
        info_row.addWidget(QLabel("호차"))
        info_row.addWidget(self.in_car)
        info_row.addWidget(QLabel("편성"))
        info_row.addWidget(self.in_formation)
        card_layout.addLayout(info_row)

        # 추가정보(가이드/툴팁)
        self.in_extra = QTextEdit()
        self.in_extra.setFixedHeight(90)
        self.in_extra.setPlaceholderText(
            "추가정보 작성 가이드(필요한 것만 기입)\n"
            "- 발생역: \n"
            "- 발생시간: \n"
            "- 발생증상: \n"
            "- 발생장치: \n"
            "- 참고: (정상/고장 비교, 다른 열번 같은 역 비교, 특정 조건 구간 등)\n"
        )
        self.in_extra.setToolTip(
            "예시:\n"
            "발생역=서면, 발생시간=18:09:12, 발생증상=정위치 불가 추정,\n"
            "발생장치=ATO/TASC, 참고=타임라인으로 표시한 구간 중심으로 원인 후보 추론"
        )
        card_layout.addWidget(QLabel("추가정보"))
        card_layout.addWidget(self.in_extra)

        # 채팅 히스토리
        self.txt_chat = QTextEdit()
        self.txt_chat.setReadOnly(True)
        self.txt_chat.setPlaceholderText("대화 내용이 여기에 표시됩니다.")
        self.txt_chat.setMinimumHeight(240)
        card_layout.addWidget(self.txt_chat, stretch=1)

        # 입력 + 전송
        bottom = QHBoxLayout()
        self.in_msg = QLineEdit()
        self.in_msg.setPlaceholderText("요청을 입력하세요. (예: 유사기록 검색 / 특정 조건 구간 분석 / 역명 기준 비교 등)")
        self.in_msg.setFixedHeight(34)

        btn_send = QPushButton("전송")
        btn_send.setFixedHeight(34)
        btn_send.clicked.connect(self.send_message)
        btn_send.setStyleSheet(
            "QPushButton{background:#0078d4;color:white;border:none;border-radius:8px;padding:6px 14px;font-weight:bold;} "
            "QPushButton:hover{background:#1084d8;} "
            "QPushButton:pressed{background:#006cc1;}"
        )
        bottom.addWidget(self.in_msg, stretch=1)
        bottom.addWidget(btn_send)
        card_layout.addLayout(bottom)

        root.addWidget(self.card)

    def _open_settings(self):
        dlg = APIKeyDialog(self)
        dlg.settings_changed.connect(self.refresh_header)
        dlg.exec()

    def refresh_header(self):
        # AI 상태
        st = get_ai_client().get_status()
        if st.get("ready"):
            self.lbl_ai_status.setText(f"AI: {st.get('provider')} / {st.get('model')}")
            self.lbl_ai_status.setStyleSheet("color: #00D084;")
        else:
            self.lbl_ai_status.setText("AI: 미준비")
            self.lbl_ai_status.setStyleSheet("color: #FFAB00;")

        # 패널 컨텍스트에서 기본정보 자동 채움(비어있을 때만)
        ctx = self._get_panel_ctx(self.cmb_panel.currentData(), None)
        date = (ctx.get("data_date") or "") if isinstance(ctx, dict) else ""
        record = (ctx.get("record") or {}) if isinstance(ctx, dict) else {}
        trainno = str(record.get("trainno") or "")

        if date and not self.in_date.text().strip():
            self.in_date.setText(date)
        if trainno and not self.in_trainno.text().strip():
            self.in_trainno.setText(trainno)

    def _append(self, who: str, text: str):
        self.txt_chat.append(f"[{who}]\n{text}\n")
        sb = self.txt_chat.verticalScrollBar()
        sb.setValue(sb.maximum())

    def _build_prompt(self, user_text: str) -> str:
        panel_id = self.cmb_panel.currentData()
        ctx = self._get_panel_ctx(panel_id, user_text) or {}

        basic = {
            "date": self.in_date.text().strip(),
            "trainno": self.in_trainno.text().strip(),
            "car_no": self.in_car.text().strip(),
            "formation": self.in_formation.text().strip(),
        }
        extra = (self.in_extra.toPlainText() or "").strip()

        # 정비지침서 발췌(유사기록/조건분석 요청에도 도움)
        kb = get_maintenance_kb()
        kb_hits = kb.search(" ".join([user_text, extra]), top_k=3)

        payload = {
            "basic": basic,
            "extra": extra,
            "panel_id": panel_id,
            "ctx": ctx,
            "kb_hits": kb_hits,
            "user_text": user_text,
        }
        safe_payload = _make_json_safe(payload)
        self._last_payload = safe_payload
        self._auto_save_payload(safe_payload)

        return (
            "당신은 철도 차량 MMI 로그를 분석하는 AI 어시스턴트입니다.\n"
            "사용자 요청에 대해, 가능한 경우 데이터 근거(필드명/타임라인/역 전후2개)를 들어 설명하세요.\n"
            "불확실하면 추가로 필요한 데이터/확인 포인트를 질문 형태로 제시하세요.\n\n"
            f"[기본정보]\n{json.dumps(_make_json_safe(basic), ensure_ascii=False, indent=2)}\n"
            f"\n[추가정보]\n{extra}\n"
            f"\n[현재 화면 컨텍스트]\n{json.dumps(_make_json_safe(ctx), ensure_ascii=False, indent=2)}\n"
            + (f"\n[정비지침서/지식 발췌]\n{json.dumps(_make_json_safe(kb_hits), ensure_ascii=False, indent=2)}\n" if kb_hits else "")
            + f"\n[사용자 요청]\n{user_text}\n"
        )

    def _auto_save_payload(self, payload: Dict[str, Any]):
        try:
            out_dir = Path.home() / ".mmi_analyzer" / "ai_debug"
            out_dir.mkdir(parents=True, exist_ok=True)
            path = out_dir / "last_payload.json"
            path.write_text(json.dumps(_make_json_safe(payload), ensure_ascii=False, indent=2), encoding="utf-8")
        except Exception:
            pass

    def show_last_payload(self):
        if not self._last_payload:
            QMessageBox.information(self, "AI", "아직 전송된 payload가 없습니다.")
            return
        dlg = QDialog(self)
        dlg.setWindowTitle("AI Payload (JSON)")
        dlg.resize(720, 520)
        lay = QVBoxLayout(dlg)
        txt = QTextEdit()
        txt.setReadOnly(True)
        txt.setText(json.dumps(_make_json_safe(self._last_payload), ensure_ascii=False, indent=2))
        lay.addWidget(txt, stretch=1)
        btns = QDialogButtonBox(QDialogButtonBox.Close)
        btns.rejected.connect(dlg.reject)
        btns.accepted.connect(dlg.accept)
        lay.addWidget(btns)
        dlg.exec()

    def send_message(self):
        text = (self.in_msg.text() or "").strip()
        if not text:
            return
        if self._busy:
            QMessageBox.information(self, "AI", "이미 요청이 실행 중입니다. 완료 후 다시 시도하세요.")
            return

        self.in_msg.clear()
        self._append("사용자", text)

        prompt = self._build_prompt(text)
        self._run(prompt)

    def _run(self, prompt: str):
        self._busy = True
        self._append("AI", "(응답 생성 중...)")

        self._thread = QThread(self)
        self._worker = _AIWorker(prompt)
        self._worker.moveToThread(self._thread)
        self._thread.started.connect(self._worker.run)
        self._worker.finished.connect(self._on_done)
        self._worker.failed.connect(self._on_fail)
        self._worker.finished.connect(self._thread.quit)
        self._worker.failed.connect(self._thread.quit)
        self._thread.finished.connect(self._on_thread_finished)
        self._thread.start()

    def _on_done(self, text: str):
        # 마지막 "(응답 생성 중...)" 줄은 단순히 구분만 하고 새 답변을 추가
        self._append("AI", text.strip())

    def _on_fail(self, err: str):
        self._append("AI", f"[오류] {err}")

    def _on_thread_finished(self):
        self._thread = None
        self._worker = None
        self._busy = False


def animate_open(dialog: QDialog, start_rect: QRect, end_rect: QRect, duration_ms: int = 220):
    dialog.setGeometry(start_rect)
    dialog.setWindowOpacity(0.0)
    dialog.show()
    anim = QPropertyAnimation(dialog, b"geometry", dialog)
    anim.setDuration(duration_ms)
    anim.setStartValue(start_rect)
    anim.setEndValue(end_rect)
    anim.setEasingCurve(QEasingCurve.OutCubic)
    anim.start()
    dialog._open_anim = anim  # GC 방지

    fade = QPropertyAnimation(dialog, b"windowOpacity", dialog)
    fade.setDuration(max(120, duration_ms))
    fade.setStartValue(0.0)
    fade.setEndValue(1.0)
    fade.setEasingCurve(QEasingCurve.OutCubic)
    fade.start()
    dialog._open_fade = fade


def animate_close(dialog: QDialog, start_rect: QRect, end_rect: QRect, duration_ms: int = 180):
    anim = QPropertyAnimation(dialog, b"geometry", dialog)
    anim.setDuration(duration_ms)
    anim.setStartValue(start_rect)
    anim.setEndValue(end_rect)
    anim.setEasingCurve(QEasingCurve.InCubic)
    anim.finished.connect(dialog.close)
    anim.start()
    dialog._close_anim = anim

    fade = QPropertyAnimation(dialog, b"windowOpacity", dialog)
    fade.setDuration(max(100, duration_ms))
    fade.setStartValue(dialog.windowOpacity())
    fade.setEndValue(0.0)
    fade.setEasingCurve(QEasingCurve.InCubic)
    fade.start()
    dialog._close_fade = fade


