from __future__ import annotations

import io
import re
import zipfile
from datetime import date, datetime
from typing import Any
from xml.etree import ElementTree as ET

import httpx
from sqlalchemy.orm import Session

from app import models
from app.config import get_settings
from app.services.mock_data_service import read_sample_transcript


FEISHU_OPEN_API = "https://open.feishu.cn/open-apis"

STATUS_TO_FEISHU = {
    models.ProcessStatus.unprocessed.value: "unprocessed",
    models.ProcessStatus.processing.value: "processing",
    models.ProcessStatus.parsed.value: "parsed",
    models.ProcessStatus.pending_review.value: "pending_review",
    models.ProcessStatus.completed.value: "completed",
    models.ProcessStatus.failed.value: "failed",
}

FEISHU_TO_STATUS = {
    "unprocessed": models.ProcessStatus.unprocessed,
    "未处理": models.ProcessStatus.unprocessed,
    "processing": models.ProcessStatus.processing,
    "处理中": models.ProcessStatus.processing,
    "parsed": models.ProcessStatus.parsed,
    "已拆解": models.ProcessStatus.parsed,
    "pending_review": models.ProcessStatus.pending_review,
    "待审核": models.ProcessStatus.pending_review,
    "completed": models.ProcessStatus.completed,
    "已完成": models.ProcessStatus.completed,
    "failed": models.ProcessStatus.failed,
    "失败": models.ProcessStatus.failed,
}

SESSION_FIELD_ALIASES = {
    "session_code": ["session_code", "场次编号", "编号"],
    "title": ["title", "标题", "答疑标题", "场次标题"],
    "date": ["date", "日期", "答疑日期"],
    "teachers": ["teachers", "答疑老师", "老师"],
    "feishu_doc_url": ["feishu_doc_url", "飞书资料链接", "文档链接", "资料链接"],
    "source_file_name": ["source_file_name", "文件名", "来源文件名"],
    "source_text": ["source_text", "转写稿", "文字稿", "原始资料", "答疑文字稿"],
    "process_status": ["process_status", "处理状态", "状态"],
    "failed_reason": ["failed_reason", "失败原因"],
}

SESSION_WRITE_FIELDS = {
    "process_status": ["处理状态", "process_status"],
    "qa_count": ["问答总数", "qa_count"],
    "pending_review_count": ["待审核数", "pending_review_count"],
    "approved_count": ["已审核数", "approved_count"],
    "standard_qa_count": ["已入库数", "standard_qa_count"],
    "failed_reason": ["失败原因", "failed_reason"],
}


def _first_field(fields: dict[str, Any], names: list[str], default: Any = None) -> Any:
    for name in names:
        if name in fields and fields[name] not in (None, ""):
            return fields[name]
    return default


def _stringify(value: Any) -> str | None:
    if value is None:
        return None
    if isinstance(value, str):
        return value
    if isinstance(value, list):
        parts: list[str] = []
        for item in value:
            if isinstance(item, dict):
                parts.append(str(item.get("text") or item.get("name") or item.get("url") or item))
            else:
                parts.append(str(item))
        return " ".join(parts)
    if isinstance(value, dict):
        return str(value.get("text") or value.get("name") or value.get("url") or value)
    return str(value)


def _parse_date(value: Any) -> date | None:
    if value in (None, ""):
        return None
    if isinstance(value, (int, float)):
        # Bitable date fields are often millisecond timestamps.
        return datetime.fromtimestamp(value / 1000).date()
    text = _stringify(value)
    if not text:
        return None
    for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%Y.%m.%d"):
        try:
            return datetime.strptime(text[:10], fmt).date()
        except ValueError:
            continue
    return None


def _extract_url(value: Any) -> str | None:
    if not value:
        return None
    if isinstance(value, list):
        for item in value:
            url = _extract_url(item)
            if url:
                return url
        return None
    if isinstance(value, dict):
        for key in ("link", "url", "text"):
            url = _extract_url(value.get(key))
            if url:
                return url
        return None
    text = str(value)
    match = re.search(r"https?://\S+", text)
    return match.group(0) if match else text if text.startswith("http") else None


def _extract_doc_id(url: str | None) -> str | None:
    if not url:
        return None
    patterns = [
        r"/docx/([A-Za-z0-9]+)",
        r"/docs/([A-Za-z0-9]+)",
        r"/wiki/([A-Za-z0-9]+)",
    ]
    for pattern in patterns:
        match = re.search(pattern, url)
        if match:
            return match.group(1)
    tail = url.rstrip("/").split("/")[-1]
    return tail if re.match(r"^[A-Za-z0-9]{8,}$", tail) else None


def _extract_drive_file_token(url: str | None) -> str | None:
    if not url:
        return None
    match = re.search(r"/file/([A-Za-z0-9]+)", url)
    return match.group(1) if match else None


class FeishuAPIError(RuntimeError):
    pass


class FeishuClient:
    """Feishu adapter used by the black-light workflow.

    Product boundary: Feishu is the material entrance. AI only cleans and
    pre-screens; every reusable QA still must pass human review in this system.
    """

    def __init__(self) -> None:
        self.settings = get_settings()
        self._tenant_access_token: str | None = None
        self._bitable_app_token: str | None = None

    @property
    def is_mock_mode(self) -> bool:
        return self.settings.mock_mode or not self.settings.feishu_configured

    def connection_status(self) -> dict[str, Any]:
        configured = self.settings.feishu_configured
        status = {
            "configured": configured,
            "mock_mode": self.is_mock_mode,
            "app_token_configured": bool(self.settings.feishu_app_token),
            "wiki_node_token_configured": bool(self.settings.feishu_wiki_node_token),
            "table_ids": {
                "session": bool(self.settings.feishu_table_id_session),
                "raw_qa": bool(self.settings.feishu_table_id_raw_qa),
                "standard_qa": bool(self.settings.feishu_table_id_standard_qa),
            },
            "token_ok": False,
            "message": "未配置飞书密钥，当前使用 mock mode。",
        }
        if not configured:
            return status
        try:
            self._get_tenant_access_token()
            self._get_bitable_app_token()
            status["token_ok"] = True
            status["message"] = "飞书 tenant_access_token 获取成功。"
        except Exception as exc:  # noqa: BLE001
            status["message"] = f"飞书连接失败：{exc}"
        return status

    def scan_unprocessed_sessions(self, db: Session) -> list[models.FeishuSession]:
        if self.is_mock_mode:
            return (
                db.query(models.FeishuSession)
                .filter(models.FeishuSession.process_status == models.ProcessStatus.unprocessed)
                .order_by(models.FeishuSession.created_at.asc())
                .all()
            )

        records = self._list_records(self.settings.feishu_table_id_session)
        sessions: list[models.FeishuSession] = []
        for record in records:
            fields = record.get("fields", {})
            record_id = record.get("record_id")
            status_text = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["process_status"], "unprocessed"))
            process_status = FEISHU_TO_STATUS.get(status_text or "", models.ProcessStatus.unprocessed)
            if process_status != models.ProcessStatus.unprocessed:
                continue

            session_code = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["session_code"])) or f"FS-{record_id}"
            session = (
                db.query(models.FeishuSession)
                .filter(models.FeishuSession.feishu_record_id == record_id)
                .one_or_none()
            )
            if not session:
                session = models.FeishuSession(session_code=session_code, title=session_code)
                db.add(session)
            session.session_code = session_code
            session.title = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["title"], session_code)) or session_code
            session.date = _parse_date(_first_field(fields, SESSION_FIELD_ALIASES["date"]))
            session.teachers = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["teachers"]))
            session.feishu_doc_url = _extract_url(_first_field(fields, SESSION_FIELD_ALIASES["feishu_doc_url"]))
            session.feishu_record_id = record_id
            session.source_file_name = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["source_file_name"]))
            session.source_text = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["source_text"]))
            session.process_status = process_status
            session.failed_reason = _stringify(_first_field(fields, SESSION_FIELD_ALIASES["failed_reason"]))
            sessions.append(session)
        db.commit()
        for session in sessions:
            db.refresh(session)
        return sessions

    def fetch_transcript(self, session: models.FeishuSession) -> str:
        if self.is_mock_mode:
            return session.source_text or read_sample_transcript()
        if session.source_text:
            return session.source_text
        file_token = _extract_drive_file_token(session.feishu_doc_url)
        if file_token:
            return self._get_drive_docx_text(file_token)
        doc_id = _extract_doc_id(session.feishu_doc_url)
        if doc_id:
            return self._get_docx_raw_content(doc_id)
        raise FeishuAPIError("场次没有转写稿文本，也没有可识别的飞书 docx 文档链接")

    def update_session_status(self, session: models.FeishuSession, status: str) -> None:
        if self.is_mock_mode or not session.feishu_record_id:
            return
        fields = self._build_session_update_fields(session, status)
        self._update_record(self.settings.feishu_table_id_session, session.feishu_record_id, fields)

    def write_raw_qa_items(self, session: models.FeishuSession, items: list[models.RawQAItem]) -> None:
        if self.is_mock_mode or not self.settings.feishu_table_id_raw_qa or not items:
            return
        self._write_raw_qa_items_compatible(session, items)
        return
        records = []
        for item in items:
            records.append(
                {
                    "fields": {
                        "问答编号": item.qa_code,
                        "场次编号": session.session_code,
                        "原始问题": item.raw_question,
                        "问题整理版": item.normalized_question,
                        "原始回答": item.raw_answer,
                        "回答整理版": item.normalized_answer,
                        "AI建议标准问题": item.suggested_standard_question,
                        "AI建议标准回答": item.suggested_standard_answer,
                        "回答人": item.answer_person,
                        "一级主题": item.primary_topic.value,
                        "问题标签": "、".join(item.problem_tags or []),
                        "课程阶段": item.course_stage.value,
                        "适用人群": "、".join(item.audience_tags or []),
                        "情绪强度": item.emotion_intensity,
                        "风险等级": item.risk_level.value,
                        "风险类型": "、".join(item.risk_types or []),
                        "风险说明": item.risk_notes,
                        "是否需脱敏": "是" if item.need_desensitization else "否",
                        "脱敏状态": item.desensitization_status.value,
                        "审核状态": item.review_status.value,
                        "建议入库": "是" if item.suggested_to_standard_qa else "否",
                        "来源时间戳": item.source_timestamp,
                        "审核备注": item.review_notes,
                    }
                }
            )
        self._batch_create_records(self.settings.feishu_table_id_raw_qa, records)

    def write_standard_qa_items(self, items: list[models.StandardQAItem]) -> None:
        if self.is_mock_mode or not self.settings.feishu_table_id_standard_qa or not items:
            return
        self._write_standard_qa_items_compatible(items)
        return
        records = []
        for item in items:
            records.append(
                {
                    "fields": {
                        "标准编号": item.standard_code,
                        "来源原始问答ID": str(item.source_raw_qa_id),
                        "来源场次ID": str(item.session_id),
                        "标准问题": item.standard_question,
                        "标准回答": item.standard_answer,
                        "相似问法": "、".join(item.similar_questions or []),
                        "一级主题": item.primary_topic.value,
                        "问题标签": "、".join(item.problem_tags or []),
                        "课程阶段": item.course_stage.value,
                        "适用人群": "、".join(item.audience_tags or []),
                        "回答边界": item.answer_boundary,
                        "禁止表达": "、".join(item.forbidden_expressions or []),
                        "风险等级": item.risk_level.value,
                        "审核状态": item.audit_status.value,
                        "调用状态": item.call_status.value,
                        "禁用原因": item.disabled_reason,
                    }
                }
            )
        self._batch_create_records(self.settings.feishu_table_id_standard_qa, records)

    def _get_tenant_access_token(self) -> str:
        if self._tenant_access_token:
            return self._tenant_access_token
        payload = {"app_id": self.settings.feishu_app_id, "app_secret": self.settings.feishu_app_secret}
        with httpx.Client(timeout=20) as client:
            response = client.post(f"{FEISHU_OPEN_API}/auth/v3/tenant_access_token/internal", json=payload)
        data = response.json()
        if response.status_code >= 400 or data.get("code") != 0:
            raise FeishuAPIError(data.get("msg") or response.text)
        self._tenant_access_token = data["tenant_access_token"]
        return self._tenant_access_token

    def _headers(self) -> dict[str, str]:
        return {"Authorization": f"Bearer {self._get_tenant_access_token()}", "Content-Type": "application/json"}

    def _get_bitable_app_token(self) -> str:
        if self._bitable_app_token:
            return self._bitable_app_token
        if self.settings.feishu_app_token:
            self._bitable_app_token = self.settings.feishu_app_token
            return self._bitable_app_token
        if not self.settings.feishu_wiki_node_token:
            raise FeishuAPIError("缺少 FEISHU_APP_TOKEN 或 FEISHU_WIKI_NODE_TOKEN")
        data = self._request(
            "GET",
            "/wiki/v2/spaces/get_node",
            params={"token": self.settings.feishu_wiki_node_token},
        )
        node = data.get("node", {})
        if node.get("obj_type") != "bitable":
            raise FeishuAPIError(f"FEISHU_WIKI_NODE_TOKEN 指向的对象不是 bitable：{node.get('obj_type')}")
        obj_token = node.get("obj_token")
        if not obj_token:
            raise FeishuAPIError("wiki 节点信息没有返回 obj_token")
        self._bitable_app_token = obj_token
        return self._bitable_app_token

    def _request(self, method: str, path: str, **kwargs: Any) -> dict[str, Any]:
        with httpx.Client(timeout=30) as client:
            response = client.request(method, f"{FEISHU_OPEN_API}{path}", headers=self._headers(), **kwargs)
        data = response.json()
        if response.status_code >= 400 or data.get("code") != 0:
            raise FeishuAPIError(data.get("msg") or response.text)
        return data.get("data", {})

    def _table_field_names(self, table_id: str | None) -> set[str]:
        if not table_id:
            return set()
        cache = getattr(self, "_table_field_name_cache", None)
        if cache is None:
            cache = {}
            self._table_field_name_cache = cache
        if table_id in cache:
            return cache[table_id]
        names: set[str] = set()
        page_token: str | None = None
        while True:
            params = {"page_size": 100}
            if page_token:
                params["page_token"] = page_token
            data = self._request(
                "GET",
                f"/bitable/v1/apps/{self._get_bitable_app_token()}/tables/{table_id}/fields",
                params=params,
            )
            names.update(item.get("field_name") for item in data.get("items", []) if item.get("field_name"))
            if not data.get("has_more"):
                break
            page_token = data.get("page_token")
        cache[table_id] = names
        return names

    def _filter_existing_fields(self, table_id: str | None, fields: dict[str, Any]) -> dict[str, Any]:
        names = self._table_field_names(table_id)
        return {key: value for key, value in fields.items() if key in names and value is not None}

    @staticmethod
    def _join_values(values: list[str] | None) -> str | None:
        return "、".join(values or []) or None

    @staticmethod
    def _enum_value(value: Any) -> str:
        return getattr(value, "value", value)

    def _risk_label(self, value: Any) -> str:
        return {"low": "低风险", "medium": "中风险", "high": "高风险"}.get(self._enum_value(value), str(value))

    def _review_label(self, value: Any) -> str:
        return {
            "pending": "待审核",
            "approved": "已通过",
            "rejected": "已拒绝",
            "needs_revision": "需修改",
        }.get(self._enum_value(value), str(value))

    def _build_session_update_fields_compatible(self, session: models.FeishuSession, status: str) -> dict[str, Any]:
        status_label = {
            models.ProcessStatus.unprocessed.value: "待处理",
            models.ProcessStatus.processing.value: "处理中",
            models.ProcessStatus.parsed.value: "处理中",
            models.ProcessStatus.pending_review.value: "待审核",
            models.ProcessStatus.completed.value: "已完成",
            models.ProcessStatus.failed.value: "失败",
        }.get(status, status)
        high_risk_count = sum(1 for item in session.raw_items if item.risk_level == models.RiskLevel.high)
        fields = {
            "处理状态": status_label,
            "问答总数": session.qa_count,
            "原始问答数": session.qa_count,
            "待审核数": session.pending_review_count,
            "已审核数": session.approved_count,
            "已入库数": session.standard_qa_count,
            "标准问答数": session.standard_qa_count,
            "高风险数": high_risk_count,
            "失败原因": session.failed_reason,
            "最近同步时间": int(datetime.now().timestamp() * 1000),
        }
        return self._filter_existing_fields(self.settings.feishu_table_id_session, fields)

    def _write_raw_qa_items_compatible(self, session: models.FeishuSession, items: list[models.RawQAItem]) -> None:
        table_id = self.settings.feishu_table_id_raw_qa
        records = []
        for item in items:
            tags = (item.problem_tags or []) + [self._enum_value(item.primary_topic)]
            fields = {
                "问答编号": item.qa_code,
                "场次编号": session.session_code,
                "原始问题": item.raw_question,
                "问题": item.normalized_question or item.raw_question,
                "问题整理版": item.normalized_question,
                "原始回答": item.raw_answer,
                "回答": item.normalized_answer or item.raw_answer,
                "回答整理版": item.normalized_answer,
                "AI建议标准问题": item.suggested_standard_question,
                "AI建议标准答案": item.suggested_standard_answer,
                "答疑老师": item.answer_person or session.teachers,
                "标签": self._join_values(tags),
                "风险等级": self._risk_label(item.risk_level),
                "证据片段": item.source_timestamp or (item.raw_answer or "")[:240],
                "审核状态": self._review_label(item.review_status),
                "入库状态": "建议入库" if item.suggested_to_standard_qa else "未入库",
            }
            filtered = self._filter_existing_fields(table_id, fields)
            if filtered:
                records.append({"fields": filtered})
        self._batch_create_records(table_id, records)

    def _write_standard_qa_items_compatible(self, items: list[models.StandardQAItem]) -> None:
        table_id = self.settings.feishu_table_id_standard_qa
        records = []
        for item in items:
            fields = {
                "标准编号": item.standard_code,
                "来源原始问答ID": str(item.source_raw_qa_id),
                "来源场次ID": str(item.session_id),
                "标准问题": item.standard_question,
                "标准答案": item.standard_answer,
                "相似问法": self._join_values(item.similar_questions),
                "一级主题": self._enum_value(item.primary_topic),
                "问题标签": self._join_values(item.problem_tags),
                "适用人群": self._join_values(item.audience_tags),
                "标签": self._join_values(item.problem_tags),
                "回答边界": item.answer_boundary,
                "禁止表达": self._join_values(item.forbidden_expressions),
                "风险等级": self._risk_label(item.risk_level),
                "审核状态": self._enum_value(item.audit_status),
                "调用状态": self._enum_value(item.call_status),
                "状态": self._enum_value(item.audit_status),
                "是否可调用": "是" if item.call_status == models.CallStatus.callable else "否",
                "来源场次": str(item.session_id),
                "来源问题": str(item.source_raw_qa_id),
                "禁用原因": item.disabled_reason,
            }
            filtered = self._filter_existing_fields(table_id, fields)
            if filtered:
                records.append({"fields": filtered})
        self._batch_create_records(table_id, records)

    def _list_records(self, table_id: str | None) -> list[dict[str, Any]]:
        if not table_id:
            raise FeishuAPIError("缺少飞书场次表 table_id")
        records: list[dict[str, Any]] = []
        page_token: str | None = None
        while True:
            params = {"page_size": 500}
            if page_token:
                params["page_token"] = page_token
            data = self._request(
                "GET",
                f"/bitable/v1/apps/{self._get_bitable_app_token()}/tables/{table_id}/records",
                params=params,
            )
            records.extend(data.get("items", []))
            if not data.get("has_more"):
                break
            page_token = data.get("page_token")
        return records

    def _update_record(self, table_id: str | None, record_id: str, fields: dict[str, Any]) -> None:
        if not table_id:
            return
        self._request(
            "PUT",
            f"/bitable/v1/apps/{self._get_bitable_app_token()}/tables/{table_id}/records/{record_id}",
            json={"fields": fields},
        )

    def _batch_create_records(self, table_id: str | None, records: list[dict[str, Any]]) -> None:
        if not table_id:
            return
        for start in range(0, len(records), 500):
            chunk = records[start : start + 500]
            self._request(
                "POST",
                f"/bitable/v1/apps/{self._get_bitable_app_token()}/tables/{table_id}/records/batch_create",
                json={"records": chunk},
            )

    def _get_docx_raw_content(self, document_id: str) -> str:
        data = self._request("GET", f"/docx/v1/documents/{document_id}/raw_content")
        content = data.get("content") or data.get("raw_content") or data.get("text")
        if not content:
            raise FeishuAPIError("飞书文档纯文本接口没有返回 content")
        return content

    def _get_drive_docx_text(self, file_token: str) -> str:
        with httpx.Client(timeout=60, follow_redirects=True) as client:
            response = client.get(
                f"{FEISHU_OPEN_API}/drive/v1/files/{file_token}/download",
                headers=self._headers(),
            )
        if response.status_code >= 400:
            try:
                data = response.json()
                message = data.get("msg") or response.text
            except Exception:  # noqa: BLE001
                message = response.text
            raise FeishuAPIError(message)
        return self._extract_docx_text(response.content)

    @staticmethod
    def _extract_docx_text(content: bytes) -> str:
        try:
            with zipfile.ZipFile(io.BytesIO(content)) as docx:
                document_xml = docx.read("word/document.xml")
        except Exception as exc:  # noqa: BLE001
            raise FeishuAPIError(f"飞书上传文件不是可解析的 docx：{exc}") from exc

        root = ET.fromstring(document_xml)
        namespace = {"w": "http://schemas.openxmlformats.org/wordprocessingml/2006/main"}
        paragraphs: list[str] = []
        for paragraph in root.findall(".//w:p", namespace):
            text = "".join(node.text or "" for node in paragraph.findall(".//w:t", namespace)).strip()
            if text:
                paragraphs.append(text)
        extracted = "\n".join(paragraphs).strip()
        if not extracted:
            raise FeishuAPIError("飞书上传 docx 没有提取到正文")
        return extracted

    def _build_session_update_fields(self, session: models.FeishuSession, status: str) -> dict[str, Any]:
        return self._build_session_update_fields_compatible(session, status)
        fields: dict[str, Any] = {
            SESSION_WRITE_FIELDS["process_status"][0]: STATUS_TO_FEISHU.get(status, status),
            SESSION_WRITE_FIELDS["qa_count"][0]: session.qa_count,
            SESSION_WRITE_FIELDS["pending_review_count"][0]: session.pending_review_count,
            SESSION_WRITE_FIELDS["approved_count"][0]: session.approved_count,
            SESSION_WRITE_FIELDS["standard_qa_count"][0]: session.standard_qa_count,
        }
        if session.failed_reason:
            fields[SESSION_WRITE_FIELDS["failed_reason"][0]] = session.failed_reason
        return fields
