#!/usr/bin/env python3
"""Docmee AiPPT provider 封装。"""

from __future__ import annotations

import json
import time
from pathlib import Path
from typing import Any

import requests

from auth import resolve_api_key


BASE_URL = "https://docmee.cn"
API_KEY = resolve_api_key()


def _headers(include_json: bool = False) -> dict[str, str]:
    headers = {
        "token": API_KEY,
        "Api-Key": API_KEY,
    }
    if include_json:
        headers["Content-Type"] = "application/json"
    return headers


def _ensure_success(response: requests.Response, step: str) -> Any:
    response.raise_for_status()
    payload = response.json()
    if payload.get("code") != 0:
        raise RuntimeError(
            f"{step}失败: code={payload.get('code')}, message={payload.get('message')}"
        )
    return payload.get("data")


def _consume_sse(response: requests.Response, step: str) -> str:
    if response.status_code != 200:
        raise RuntimeError(f"{step}失败: httpStatus={response.status_code}")

    content_type = response.headers.get("Content-Type", "")
    if "application/json" in content_type:
        payload = response.json()
        raise RuntimeError(
            f"{step}失败: code={payload.get('code')}, message={payload.get('message')}"
        )

    parts: list[str] = []
    for raw_line in response.iter_lines():
        if raw_line is None:
            continue
        # 显式使用 UTF-8 解码
        try:
            line = raw_line.decode("utf-8").strip()
        except UnicodeDecodeError:
            try:
                line = raw_line.decode("gbk").strip()
            except UnicodeDecodeError:
                continue
        
        if not line or not line.startswith("data:"):
            continue
        data = line[5:].lstrip()
        if not data or data == "[DONE]":
            continue

        try:
            event = json.loads(data)
        except json.JSONDecodeError:
            # 跳过解析失败的行
            print(f"\n[警告] 跳过解析失败的数据: {data[:50]}...")
            continue
        if event.get("status") == -1:
            raise RuntimeError(f"{step}失败: {event.get('error') or event.get('message')}")

        text = event.get("text", "")
        if text:
            # 确保 text 是正确的 UTF-8 编码
            if isinstance(text, bytes):
                text = text.decode("utf-8")
            parts.append(text)
            print(text, end="", flush=True)

    print()
    return "".join(parts)


def parse_file_data(
    *,
    file_path: str | None = None,
    content: str | None = None,
    file_url: str | None = None,
    website: str | None = None,
    websearch: str | None = None,
) -> str:
    url = f"{BASE_URL}/api/ppt/parseFileData"
    data = {
        "content": content,
        "fileUrl": file_url,
        "website": website,
        "websearch": websearch,
    }
    data = {key: value for key, value in data.items() if value}

    files = None
    if file_path:
        with open(file_path, "rb") as file:
            files = {"file": file}
            response = requests.post(url, headers=_headers(), data=data or None, files=files, timeout=300)
    else:
        response = requests.post(url, headers=_headers(), data=data or None, timeout=300)

    result = _ensure_success(response, "解析文件或内容")
    data_url = result.get("dataUrl") if isinstance(result, dict) else None
    if not data_url:
        raise RuntimeError("解析文件或内容失败: 返回结果中缺少 dataUrl")
    return str(data_url)


def generate_outline(subject: str | None, data_url: str | None, prompt: str | None) -> str:
    url = f"{BASE_URL}/api/ppt/generateOutline"
    body = json.dumps(
        {
            "subject": subject,
            "prompt": prompt,
            "dataUrl": data_url,
        },
        ensure_ascii=False,
    )
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        stream=True,
        timeout=300,
    )
    return _consume_sse(response, "生成大纲")


def generate_content(outline: str, data_url: str | None, prompt: str | None) -> str:
    url = f"{BASE_URL}/api/ppt/generateContent"
    body = json.dumps(
        {
            "outlineMarkdown": outline,
            "prompt": prompt,
            "dataUrl": data_url,
        },
        ensure_ascii=False,
    )
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        stream=True,
        timeout=300,
    )
    return _consume_sse(response, "生成大纲内容")


def random_template_id() -> str:
    url = f"{BASE_URL}/api/ppt/randomTemplates"
    body = json.dumps({"size": 1, "filters": {"type": 1}}, ensure_ascii=False)
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        timeout=60,
    )
    result = _ensure_success(response, "获取随机模板")
    if not isinstance(result, list) or not result:
        raise RuntimeError("获取随机模板失败: 模板列表为空")
    template_id = result[0].get("id")
    if not template_id:
        raise RuntimeError("获取随机模板失败: 返回结果中缺少模板 ID")
    return str(template_id)


def _extract_template_id(result: Any) -> str | None:
    if isinstance(result, str) and result:
        return result
    if not isinstance(result, dict):
        return None

    for key in ("templateId", "id"):
        value = result.get(key)
        if value:
            return str(value)

    for key in ("data", "result"):
        nested = result.get(key)
        template_id = _extract_template_id(nested)
        if template_id:
            return template_id

    return None


def upload_template(template_path: str, template_id: str | None = None) -> str:
    url = f"{BASE_URL}/api/ppt/uploadTemplate"
    data: dict[str, str] = {"type": "4"}
    if template_id:
        data["templateId"] = template_id

    with open(template_path, "rb") as file:
        response = requests.post(
            url,
            headers=_headers(),
            data=data,
            files={"file": file},
            timeout=300,
        )

    result = _ensure_success(response, "上传自定义模板")
    resolved_template_id = _extract_template_id(result)
    if not resolved_template_id:
        raise RuntimeError("上传自定义模板失败: 返回结果中缺少 templateId")
    return resolved_template_id


def generate_pptx(template_id: str, markdown: str, pptx_property: bool = False) -> dict[str, Any]:
    url = f"{BASE_URL}/api/ppt/generatePptx"
    body = json.dumps(
        {
            "templateId": template_id,
            "outlineContentMarkdown": markdown,
            "pptxProperty": pptx_property,
        },
        ensure_ascii=False,
    )
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        timeout=300,
    )
    result = _ensure_success(response, "生成 PPT")
    if not isinstance(result, dict):
        raise RuntimeError("生成 PPT 失败: 返回的 pptInfo 结构异常")
    ppt_info = result.get("pptInfo") if "pptInfo" in result else result
    if not isinstance(ppt_info, dict):
        raise RuntimeError("生成 PPT 失败: 返回结果中缺少 pptInfo")
    return ppt_info


def update_ppt_template(ppt_id: str, template_id: str, sync: bool = False) -> dict[str, Any]:
    url = f"{BASE_URL}/api/ppt/updatePptTemplate"
    body = json.dumps(
        {
            "pptId": ppt_id,
            "templateId": template_id,
            "sync": sync,
        },
        ensure_ascii=False,
    )
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        timeout=300,
    )
    result = _ensure_success(response, "更换 PPT 模板")
    if not isinstance(result, dict):
        raise RuntimeError("更换 PPT 模板失败: 返回结构异常")
    return result


def download_pptx(ppt_id: str) -> dict[str, Any]:
    url = f"{BASE_URL}/api/ppt/downloadPptx"
    body = json.dumps({"id": ppt_id}, ensure_ascii=False)
    response = requests.post(
        url,
        headers=_headers(include_json=True),
        data=body.encode("utf-8"),
        timeout=120,
    )
    result = _ensure_success(response, "获取 PPT 下载信息")
    if not isinstance(result, dict):
        raise RuntimeError("获取 PPT 下载信息失败: 返回结构异常")
    return result


def wait_for_download_url(ppt_id: str, max_polls: int = 30, interval: float = 2.0) -> str:
    for attempt in range(1, max_polls + 1):
        result = download_pptx(ppt_id)
        file_url = result.get("fileUrl")
        print(f"  [{attempt}/{max_polls}] 下载状态={'ready' if file_url else 'pending'}")
        if file_url:
            return str(file_url)
        time.sleep(interval)
    raise RuntimeError(f"等待 PPT 下载链接超时，已轮询 {max_polls} 次")


def download_file(url: str, save_path: str) -> None:
    response = requests.get(url, timeout=300)
    response.raise_for_status()
    Path(save_path).write_bytes(response.content)
