from __future__ import annotations

import json
import os
import sys
import tempfile
import uuid
from pathlib import Path
from typing import Any


VPN_TYPES = [
    "aTrust",
    "Hillstone Secure Connect",
    "easy connect",
    "FortiClient VPN",
    "网页登录",
    "无VPN",
]

NO_VPN = "无VPN"


class StorageError(Exception):
    """Raised when project data cannot be validated or persisted."""


def get_app_dir() -> Path:
    if getattr(sys, "frozen", False):
        return Path(sys.executable).resolve().parent
    return Path(__file__).resolve().parent


class ProjectStore:
    def __init__(self, data_dir: Path | None = None) -> None:
        self.data_dir = data_dir or get_app_dir()
        self.projects_path = self.data_dir / "projects.json"
        self.credentials_path = self.data_dir / "vpn_credentials.json"

    def list_projects(self) -> list[dict[str, str]]:
        projects = self._load_projects()
        credentials = self._load_credentials()

        merged: list[dict[str, str]] = []
        for project in sorted(projects, key=lambda item: item["project_name"].lower()):
            project_credentials = credentials.get(project["id"], {})
            merged.append(
                {
                    **project,
                    "vpn_user": str(project_credentials.get("vpn_user", "")),
                    "vpn_password": str(project_credentials.get("vpn_password", "")),
                }
            )
        return merged

    def get_project(self, project_id: str) -> dict[str, str] | None:
        for project in self.list_projects():
            if project["id"] == project_id:
                return project
        return None

    def create_project(
        self,
        project_name: str,
        vpn_type: str,
        mdm_version: str,
        vpn_user: str = "",
        vpn_password: str = "",
    ) -> dict[str, str]:
        projects = self._load_projects()
        project = self._validate_project(
            {
                "id": str(uuid.uuid4()),
                "project_name": project_name,
                "vpn_type": vpn_type,
                "mdm_version": mdm_version,
            },
            projects,
        )
        credentials = self._validate_credentials(vpn_type, vpn_user, vpn_password)

        projects.append(project)
        all_credentials = self._load_credentials()
        if credentials is None:
            all_credentials.pop(project["id"], None)
        else:
            all_credentials[project["id"]] = credentials

        self._write_projects(projects)
        self._write_credentials(all_credentials)
        return {**project, **(credentials or {"vpn_user": "", "vpn_password": ""})}

    def update_project(
        self,
        project_id: str,
        project_name: str,
        vpn_type: str,
        mdm_version: str,
        vpn_user: str = "",
        vpn_password: str = "",
    ) -> dict[str, str]:
        projects = self._load_projects()
        index = self._find_project_index(projects, project_id)
        if index is None:
            raise StorageError("项目不存在，无法修改。")

        updated = self._validate_project(
            {
                "id": project_id,
                "project_name": project_name,
                "vpn_type": vpn_type,
                "mdm_version": mdm_version,
            },
            projects,
            current_id=project_id,
        )
        credentials = self._validate_credentials(vpn_type, vpn_user, vpn_password)

        projects[index] = updated
        all_credentials = self._load_credentials()
        if credentials is None:
            all_credentials.pop(project_id, None)
        else:
            all_credentials[project_id] = credentials

        self._write_projects(projects)
        self._write_credentials(all_credentials)
        return {**updated, **(credentials or {"vpn_user": "", "vpn_password": ""})}

    def delete_project(self, project_id: str) -> None:
        projects = self._load_projects()
        index = self._find_project_index(projects, project_id)
        if index is None:
            raise StorageError("项目不存在，无法删除。")

        del projects[index]
        credentials = self._load_credentials()
        credentials.pop(project_id, None)

        self._write_projects(projects)
        self._write_credentials(credentials)

    def _validate_project(
        self,
        project: dict[str, str],
        existing_projects: list[dict[str, str]],
        current_id: str | None = None,
    ) -> dict[str, str]:
        project_name = project.get("project_name", "").strip()
        vpn_type = project.get("vpn_type", "").strip()
        mdm_version = project.get("mdm_version", "").strip()

        if not project_name:
            raise StorageError("项目名称不能为空。")
        if not mdm_version:
            raise StorageError("MDM版本号不能为空。")
        if vpn_type not in VPN_TYPES:
            raise StorageError("VPN登录方式无效。")

        normalized_name = project_name.casefold()
        for existing in existing_projects:
            if current_id and existing["id"] == current_id:
                continue
            if existing["project_name"].strip().casefold() == normalized_name:
                raise StorageError("项目名称已存在。")

        return {
            "id": project["id"],
            "project_name": project_name,
            "vpn_type": vpn_type,
            "mdm_version": mdm_version,
        }

    def _validate_credentials(
        self, vpn_type: str, vpn_user: str, vpn_password: str
    ) -> dict[str, str] | None:
        if vpn_type == NO_VPN:
            return None
        return {
            "vpn_user": vpn_user.strip(),
            "vpn_password": vpn_password,
        }

    def _load_projects(self) -> list[dict[str, str]]:
        raw = self._load_json(self.projects_path, [])
        if not isinstance(raw, list):
            raise StorageError("projects.json 格式错误，应为项目数组。")

        projects: list[dict[str, str]] = []
        seen_ids: set[str] = set()
        for item in raw:
            if not isinstance(item, dict):
                raise StorageError("projects.json 中存在无效项目。")
            project_id = str(item.get("id", "")).strip()
            project_name = str(item.get("project_name", "")).strip()
            vpn_type = str(item.get("vpn_type", "")).strip()
            mdm_version = str(item.get("mdm_version", "")).strip()
            if not project_id or project_id in seen_ids:
                raise StorageError("projects.json 中存在空或重复的项目ID。")
            if vpn_type not in VPN_TYPES:
                raise StorageError(f"项目 {project_name or project_id} 的 VPN 登录方式无效。")
            seen_ids.add(project_id)
            projects.append(
                {
                    "id": project_id,
                    "project_name": project_name,
                    "vpn_type": vpn_type,
                    "mdm_version": mdm_version,
                }
            )
        return projects

    def _load_credentials(self) -> dict[str, dict[str, str]]:
        raw = self._load_json(self.credentials_path, {})
        if not isinstance(raw, dict):
            raise StorageError("vpn_credentials.json 格式错误，应为对象。")

        credentials: dict[str, dict[str, str]] = {}
        for project_id, value in raw.items():
            if not isinstance(value, dict):
                raise StorageError("vpn_credentials.json 中存在无效账号密码记录。")
            credentials[str(project_id)] = {
                "vpn_user": str(value.get("vpn_user", "")),
                "vpn_password": str(value.get("vpn_password", "")),
            }
        return credentials

    def _write_projects(self, projects: list[dict[str, str]]) -> None:
        self._write_json(self.projects_path, projects)

    def _write_credentials(self, credentials: dict[str, dict[str, str]]) -> None:
        self._write_json(self.credentials_path, credentials)

    def _load_json(self, path: Path, default: Any) -> Any:
        if not path.exists():
            return default
        try:
            return json.loads(path.read_text(encoding="utf-8"))
        except json.JSONDecodeError as exc:
            raise StorageError(f"{path.name} 不是有效 JSON：{exc}") from exc
        except OSError as exc:
            raise StorageError(f"读取 {path.name} 失败：{exc}") from exc

    def _write_json(self, path: Path, data: Any) -> None:
        self.data_dir.mkdir(parents=True, exist_ok=True)
        fd, tmp_name = tempfile.mkstemp(
            prefix=f".{path.name}.", suffix=".tmp", dir=str(self.data_dir)
        )
        tmp_path = Path(tmp_name)
        try:
            with os.fdopen(fd, "w", encoding="utf-8") as file:
                json.dump(data, file, ensure_ascii=False, indent=2)
                file.write("\n")
            os.replace(tmp_path, path)
        except OSError as exc:
            raise StorageError(f"写入 {path.name} 失败：{exc}") from exc
        finally:
            if tmp_path.exists():
                try:
                    tmp_path.unlink()
                except OSError:
                    pass

    def _find_project_index(
        self, projects: list[dict[str, str]], project_id: str
    ) -> int | None:
        for index, project in enumerate(projects):
            if project["id"] == project_id:
                return index
        return None
