from __future__ import annotations

import json
import logging
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from uuid import uuid4

from .config import Settings
from .models import DesktopBridgeMessage, DesktopBridgeState, utc_now

logger = logging.getLogger(__name__)


class DesktopBridgeStore:
    def __init__(self, path: Path, inbox_path: Path) -> None:
        self.path = path
        self.path.parent.mkdir(parents=True, exist_ok=True)
        self.inbox_path = inbox_path
        self.inbox_path.parent.mkdir(parents=True, exist_ok=True)

    def load(self) -> DesktopBridgeState:
        if not self.path.exists():
            state = DesktopBridgeState()
            self.save(state)
            return state
        state = DesktopBridgeState.model_validate(json.loads(self.path.read_text(encoding="utf-8")))
        self._write_inbox_file(state)
        return state

    def save(self, state: DesktopBridgeState) -> None:
        state.updated_at = utc_now()
        self.path.write_text(
            json.dumps(state.model_dump(mode="json"), ensure_ascii=False, indent=2),
            encoding="utf-8",
        )
        self._write_inbox_file(state)

    def append_message(
        self,
        author: str,
        text: str,
        status: str = "pending",
        mirrored_to_project: bool = False,
    ) -> DesktopBridgeState:
        state = self.load()
        message = DesktopBridgeMessage(
            message_id=uuid4().hex[:12],
            author=author,
            text=text,
            status=status,
            mirrored_to_project=mirrored_to_project,
        )
        state.messages.append(message)
        state.messages = state.messages[-200:]
        self.save(state)
        return state

    def mark_mirrored(self, message_id: str) -> DesktopBridgeState:
        state = self.load()
        for message in state.messages:
            if message.message_id == message_id:
                message.mirrored_to_project = True
                break
        self.save(state)
        return state

    def mark_done(self, message_id: str) -> DesktopBridgeState:
        state = self.load()
        for message in state.messages:
            if message.message_id == message_id:
                message.status = "done"
                break
        self.save(state)
        return state

    def _write_inbox_file(self, state: DesktopBridgeState) -> None:
        pending = [message for message in state.messages if message.status == "pending" and message.author != "assistant"]
        lines = [
            "# CodexMobile Desktop Bridge Inbox",
            "",
            "這裡會列出從手機端送來、等待桌面協作處理的訊息。",
            "",
            f"待處理數量：{len(pending)}",
            "",
        ]
        if pending:
            for message in pending[-20:]:
                lines.extend(
                    [
                        f"## {message.created_at}",
                        f"- 來源：{message.author}",
                        f"- 狀態：{message.status}",
                        "",
                        message.text,
                        "",
                    ]
                )
        else:
            lines.append("目前沒有待處理的桌面橋接訊息。")
        self.inbox_path.write_text("\n".join(lines), encoding="utf-8")


class DesktopBridgeHandler(BaseHTTPRequestHandler):
    store: DesktopBridgeStore
    token: str

    def do_GET(self) -> None:  # noqa: N802
        if not self._authorize():
            return
        if self.path == "/health":
            self._send_json({"ok": True})
            return
        if self.path == "/status":
            state = self.store.load()
            pending = [message for message in state.messages if message.status == "pending" and message.author != "assistant"]
            self._send_json(
                {
                    "pending_count": len(pending),
                    "last_message": state.messages[-1].model_dump(mode="json") if state.messages else None,
                    "state": state.model_dump(mode="json"),
                }
            )
            return
        if self.path.startswith("/messages"):
            state = self.store.load()
            self._send_json({"messages": [message.model_dump(mode="json") for message in state.messages[-50:]]})
            return
        self._send_json({"error": "Not found"}, status=HTTPStatus.NOT_FOUND)

    def do_POST(self) -> None:  # noqa: N802
        if not self._authorize():
            return
        payload = self._read_json()
        if self.path == "/inbox":
            text = str(payload.get("text", "")).strip()
            author = str(payload.get("author", "mobile")).strip() or "mobile"
            mirrored_to_project = bool(payload.get("mirrored_to_project", False))
            if not text:
                self._send_json({"error": "Missing text"}, status=HTTPStatus.BAD_REQUEST)
                return
            state = self.store.append_message(
                author=author,
                text=text,
                status="pending",
                mirrored_to_project=mirrored_to_project,
            )
            created_message_id = state.messages[-1].message_id if state.messages else ""
            self._send_json({"ok": True, "message_id": created_message_id, "state": state.model_dump(mode="json")})
            return
        if self.path == "/mark_mirrored":
            message_id = str(payload.get("message_id", "")).strip()
            if not message_id:
                self._send_json({"error": "Missing message_id"}, status=HTTPStatus.BAD_REQUEST)
                return
            state = self.store.mark_mirrored(message_id)
            self._send_json({"ok": True, "state": state.model_dump(mode="json")})
            return
        if self.path == "/mark_done":
            message_id = str(payload.get("message_id", "")).strip()
            if not message_id:
                self._send_json({"error": "Missing message_id"}, status=HTTPStatus.BAD_REQUEST)
                return
            state = self.store.mark_done(message_id)
            self._send_json({"ok": True, "state": state.model_dump(mode="json")})
            return
        if self.path == "/reply":
            text = str(payload.get("text", "")).strip()
            if not text:
                self._send_json({"error": "Missing text"}, status=HTTPStatus.BAD_REQUEST)
                return
            state = self.store.append_message(author="assistant", text=text, status="done")
            self._send_json({"ok": True, "state": state.model_dump(mode="json")})
            return
        self._send_json({"error": "Not found"}, status=HTTPStatus.NOT_FOUND)

    def _authorize(self) -> bool:
        expected = f"Bearer {self.token}"
        if self.headers.get("Authorization", "") != expected:
            self._send_json({"error": "Unauthorized"}, status=HTTPStatus.UNAUTHORIZED)
            return False
        return True

    def _read_json(self) -> dict:
        length = int(self.headers.get("Content-Length", "0") or "0")
        if length <= 0:
            return {}
        raw = self.rfile.read(length)
        return json.loads(raw.decode("utf-8"))

    def _send_json(self, payload: dict, status: HTTPStatus = HTTPStatus.OK) -> None:
        encoded = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        self.send_response(status)
        self.send_header("Content-Type", "application/json; charset=utf-8")
        self.send_header("Content-Length", str(len(encoded)))
        self.end_headers()
        self.wfile.write(encoded)

    def log_message(self, format: str, *args) -> None:  # noqa: A003
        logger.info("desktop_bridge " + format, *args)


def main() -> None:
    settings = Settings()
    logging.basicConfig(
        level=getattr(logging, settings.codexbot_log_level.upper(), logging.INFO),
        format="%(asctime)s %(levelname)s %(name)s %(message)s",
    )
    token = settings.codexbot_desktop_bridge_token or settings.codexbot_mac_service_token
    if not token:
        raise RuntimeError("CODEXMOBILE_DESKTOP_BRIDGE_TOKEN or CODEXMOBILE_MAC_SERVICE_TOKEN is required")
    handler = type(
        "BoundDesktopBridgeHandler",
        (DesktopBridgeHandler,),
        {
            "store": DesktopBridgeStore(
                settings.codexbot_desktop_bridge_state_path,
                settings.codexbot_desktop_bridge_inbox_path,
            ),
            "token": token,
        },
    )
    server = ThreadingHTTPServer(
        (settings.codexbot_desktop_bridge_host, settings.codexbot_desktop_bridge_port),
        handler,
    )
    logger.info(
        "Desktop bridge listening on %s:%s",
        settings.codexbot_desktop_bridge_host,
        settings.codexbot_desktop_bridge_port,
    )
    server.serve_forever()
