"""Durable desktop projects; independent of formula implementations."""

from __future__ import annotations

import json
import os
import re
import threading
import uuid
from datetime import datetime, timezone
from pathlib import Path
from typing import Any


class WorkspaceStore:
    def __init__(self, root: Path | None = None) -> None:
        self.root = root or Path(os.environ.get("APPDATA") or Path.home()) / "HTZD" / "workspace"
        self.lock = threading.RLock()

    @staticmethod
    def now() -> str:
        return datetime.now(timezone.utc).isoformat(timespec="seconds")

    def _path(self, project_id: str) -> Path:
        if not re.fullmatch(r"PRJ-[a-f0-9]{12}", project_id):
            raise ValueError("工程编号无效")
        return self.root / f"{project_id}.json"

    def _write(self, path: Path, value: Any) -> None:
        text = json.dumps(value, ensure_ascii=False, allow_nan=False)
        path.parent.mkdir(parents=True, exist_ok=True)
        temporary = path.with_suffix(f".{uuid.uuid4().hex}.tmp")
        try:
            with temporary.open("w", encoding="utf-8") as output:
                output.write(text)
                output.flush()
                os.fsync(output.fileno())
            os.replace(temporary, path)
        finally:
            temporary.unlink(missing_ok=True)

    def read(self, project_id: str) -> dict[str, Any]:
        with self.lock:
            project = json.loads(self._path(project_id).read_text(encoding="utf-8"))
            self._validate(project)
            if project.get("id") != project_id:
                raise ValueError("工程编号与文件不一致，原文件已保留")
            return project

    @staticmethod
    def _validate(payload: Any) -> None:
        if not isinstance(payload, dict) or payload.get("schema") != 1 or not isinstance(payload.get("drafts"), dict) or not isinstance(payload.get("records"), list):
            raise ValueError("不是有效的慧通智达工程文件")
        from .program_registry import get_program
        for key, draft in payload["drafts"].items():
            get_program(key)
            if not isinstance(draft, dict) or not isinstance(draft.get("fields"), dict):
                raise ValueError("工程参数草稿格式无效")
        ids = set()
        for record in payload["records"]:
            if not isinstance(record, dict) or not isinstance(record.get("id"), str) or not isinstance(record.get("params"), dict) or not isinstance(record.get("result"), dict):
                raise ValueError("工程成果快照格式无效")
            get_program(record.get("program_id", ""))
            if record["id"] in ids:
                raise ValueError("工程成果编号重复")
            ids.add(record["id"])
        json.dumps(payload, allow_nan=False)

    def state(self) -> dict[str, Any]:
        with self.lock:
            projects, errors = [], []
            for path in self.root.glob("PRJ-*.json"):
                try:
                    project = self.read(path.stem)
                    projects.append({key: project.get(key) for key in ("id", "name", "description", "created_at", "updated_at")})
                    projects[-1].update(records=len(project.get("records", [])), drafts=len(project.get("drafts", {})))
                except (ValueError, OSError, KeyError, TypeError):
                    errors.append(path.name)
            settings = self.root / "settings.json"
            try:
                prefs = json.loads(settings.read_text(encoding="utf-8")) if settings.exists() else {}
                if not isinstance(prefs, dict):
                    raise ValueError("偏好文件无效")
                for key in ("favorites", "recent"):
                    if key in prefs and not isinstance(prefs[key], list):
                        raise ValueError("偏好列表无效")
            except (ValueError, OSError):
                prefs = {}
                errors.append("settings.json")
            return {"ok": True, "projects": sorted(projects, key=lambda p: p.get("updated_at") or "", reverse=True), "preferences": prefs, "unreadable": errors, "path": str(self.root)}

    def create(self, name: str, description: str = "") -> dict[str, Any]:
        name = str(name).strip()
        if not name or len(name) > 120:
            raise ValueError("工程名称须为 1 至 120 个字符")
        project_id = f"PRJ-{uuid.uuid4().hex[:12]}"
        project = {"schema": 1, "id": project_id, "name": name, "description": str(description)[:2000], "created_at": self.now(), "updated_at": self.now(), "drafts": {}, "records": []}
        with self.lock:
            self._write(self._path(project_id), project)
        return project

    def update(self, project_id: str, change: dict[str, Any]) -> dict[str, Any]:
        with self.lock:
            project = self.read(project_id)
            for key in ("name", "description"):
                if key in change:
                    value = str(change[key]).strip()
                    if key == "name" and (not value or len(value) > 120):
                        raise ValueError("工程名称须为 1 至 120 个字符")
                    project[key] = value[:2000]
            project["updated_at"] = self.now()
            self._write(self._path(project_id), project)
            return project

    def save_draft(self, project_id: str, program_id: str, draft: dict[str, Any]) -> dict[str, Any]:
        self._validate({"schema": 1, "drafts": {program_id: draft}, "records": []})
        with self.lock:
            project = self.read(project_id)
            project["drafts"][program_id] = {**draft, "updated_at": self.now()}
            project["updated_at"] = self.now()
            self._write(self._path(project_id), project)
            return {"ok": True, "updated_at": project["updated_at"]}

    def add_record(self, project_id: str, program_id: str, params: dict[str, Any], result: dict[str, Any]) -> dict[str, Any]:
        with self.lock:
            project = self.read(project_id)
            record = {"id": f"CALC-{uuid.uuid4().hex[:12]}", "program_id": program_id, "created_at": self.now(), "params": params, "result": result}
            self._validate({"schema": 1, "drafts": {}, "records": [record]})
            project["records"].append(record)
            project["updated_at"] = self.now()
            self._write(self._path(project_id), project)
            return {"ok": True, "record": record}

    def preferences(self, change: dict[str, Any]) -> dict[str, Any]:
        with self.lock:
            state = self.state()
            if "settings.json" in state["unreadable"]:
                raise ValueError("偏好文件损坏，请先备份并修复 settings.json")
            current = state["preferences"]
            for key in ("active_project", "favorites", "recent", "density"):
                if key in change:
                    if key in ("favorites", "recent") and (not isinstance(change[key], list) or not all(isinstance(item, str) for item in change[key])):
                        raise ValueError("收藏和最近使用必须为程序编号列表")
                    current[key] = change[key]
            self._write(self.root / "settings.json", current)
            return {"ok": True, "preferences": current}

    def import_project(self, payload: dict[str, Any]) -> dict[str, Any]:
        self._validate(payload)
        # Import always creates a new identity so existing engineering data is
        # never overwritten by a file with the same project name or ID.
        with self.lock:
            project = self.create(payload.get("name", "导入工程"), payload.get("description", ""))
            project["drafts"] = payload["drafts"]
            project["records"] = payload["records"]
            self._write(self._path(project["id"]), project)
            return project
