"""JSON-file storage and safe file helpers for uploaded accounts and outputs."""

from __future__ import annotations

import json
import re
from dataclasses import dataclass
from datetime import UTC, datetime
from pathlib import Path
from typing import Any


SAFE_NAME_RE = re.compile(r"[^a-zA-Z0-9_.-]+")


def utc_now() -> str:
    return datetime.now(UTC).isoformat(timespec="seconds")


def safe_slug(value: str, fallback: str = "file") -> str:
    cleaned = SAFE_NAME_RE.sub("_", value.strip()).strip("._")
    return cleaned[:80] or fallback


def atomic_write_json(path: Path, payload: Any) -> None:
    path = path.resolve()
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", encoding="utf-8") as handle:
        json.dump(payload, handle, ensure_ascii=False, indent=2)
        handle.write("\n")


def validate_accounts(payload: Any) -> list[dict[str, str]]:
    if not isinstance(payload, list):
        raise ValueError("JSON root must be a list")
    accounts: list[dict[str, str]] = []
    for index, item in enumerate(payload, start=1):
        if not isinstance(item, dict):
            raise ValueError(f"item {index} must be an object")
        raw_uid = item.get("uid")
        raw_password = item.get("password")
        uid = "" if raw_uid is None else str(raw_uid).strip()
        password = "" if raw_password is None else str(raw_password)
        if not uid or not password:
            raise ValueError(f"item {index} must contain uid and password")
        accounts.append({"uid": uid, "password": password})
    if not accounts:
        raise ValueError("account list is empty")
    return accounts


@dataclass(frozen=True)
class AccountFile:
    id: int
    filename: str
    display_name: str
    server: str
    purpose: str
    account_count: int
    status: str
    order_index: int
    created_at: str
    last_used_at: str | None


class Storage:
    def __init__(self, db_path: Path, uploads_dir: Path, generated_dir: Path, failed_dir: Path):
        self.db_path = db_path
        self.uploads_dir = uploads_dir
        self.generated_dir = generated_dir
        self.failed_dir = failed_dir

    def init_db(self) -> None:
        if not self.db_path.exists():
            self._save(self._empty_db())

    def _empty_db(self) -> dict[str, Any]:
        return {
            "next_file_id": 1,
            "next_generation_id": 1,
            "next_schedule_id": 1,
            "account_files": [],
            "queue_pointers": {},
            "generations": [],
            "last_generations": {},
            "schedules": [],
        }

    def _load(self) -> dict[str, Any]:
        if not self.db_path.exists():
            return self._empty_db()
        with self.db_path.open("r", encoding="utf-8") as handle:
            data = json.load(handle)
        base = self._empty_db()
        base.update(data if isinstance(data, dict) else {})
        return base

    def _save(self, data: dict[str, Any]) -> None:
        atomic_write_json(self.db_path, data)

    def _next_id(self, data: dict[str, Any], key: str) -> int:
        value = int(data.get(key, 1))
        data[key] = value + 1
        return value

    def add_account_file(
        self,
        *,
        display_name: str,
        server: str,
        purpose: str,
        order_index: int,
        accounts: list[dict[str, str]],
    ) -> AccountFile:
        data = self._load()
        existing = [
            item
            for item in data["account_files"]
            if item["server"] == server and item["purpose"] == purpose
        ]
        if order_index <= 0:
            order_index = len(existing) + 1

        file_id = self._next_id(data, "next_file_id")
        safe_name = safe_slug(display_name)
        filename = f"{file_id:04d}_{server}_{purpose}_{safe_name}.json"
        row = {
            "id": file_id,
            "filename": filename,
            "display_name": display_name,
            "server": server,
            "purpose": purpose,
            "account_count": len(accounts),
            "status": "enabled",
            "order_index": order_index,
            "created_at": utc_now(),
            "last_used_at": None,
        }
        atomic_write_json(self.uploads_dir / filename, accounts)
        data["account_files"].append(row)
        self._save(data)
        return self._file_from_row(row)

    def list_files(self, server: str | None = None, purpose: str | None = None) -> list[AccountFile]:
        rows = self._load()["account_files"]
        if server:
            rows = [row for row in rows if row["server"] == server]
        if purpose:
            rows = [row for row in rows if row["purpose"] == purpose]
        rows = sorted(rows, key=lambda row: (row["server"], row["purpose"], row["order_index"], row["id"]))
        return [self._file_from_row(row) for row in rows]

    def get_file(self, file_id: int) -> AccountFile | None:
        for row in self._load()["account_files"]:
            if int(row["id"]) == file_id:
                return self._file_from_row(row)
        return None

    def load_accounts(self, file: AccountFile) -> list[dict[str, str]]:
        with (self.uploads_dir / file.filename).open("r", encoding="utf-8") as handle:
            return validate_accounts(json.load(handle))

    def enabled_files(self, server: str, purpose: str) -> list[AccountFile]:
        rows = [
            row
            for row in self._load()["account_files"]
            if row["server"] == server and row["purpose"] == purpose and row["status"] == "enabled"
        ]
        rows = sorted(rows, key=lambda row: (row["order_index"], row["id"]))
        return [self._file_from_row(row) for row in rows]

    def next_file(self, server: str, purpose: str, queue_mode: str) -> AccountFile | None:
        result = self.next_file_with_meta(server, purpose, queue_mode)
        return result["file"] if result else None

    def next_file_with_meta(self, server: str, purpose: str, queue_mode: str) -> dict[str, Any] | None:
        data = self._load()
        files = [
            row
            for row in data["account_files"]
            if row["server"] == server and row["purpose"] == purpose and row["status"] == "enabled"
        ]
        files = sorted(files, key=lambda row: (row["order_index"], row["id"]))
        if not files:
            return None

        key = f"{server}:{purpose}"
        next_index = int(data["queue_pointers"].get(key, 0))
        if next_index >= len(files):
            if queue_mode == "stop":
                return None
            next_index = 0

        selected = files[next_index]
        new_index = next_index + 1
        if new_index >= len(files):
            data["queue_pointers"][key] = 0 if queue_mode == "cycle" else len(files)
            next_file_index = 1 if queue_mode == "cycle" else None
        else:
            data["queue_pointers"][key] = new_index
            next_file_index = new_index + 1
        self._save(data)
        return {
            "file": self._file_from_row(selected),
            "file_index": next_index + 1,
            "file_total": len(files),
            "next_file_index": next_file_index,
        }

    def queue_metadata_for_file(self, file: AccountFile, queue_mode: str) -> dict[str, Any]:
        data = self._load()
        files = [
            row
            for row in data["account_files"]
            if row["server"] == file.server and row["purpose"] == file.purpose and row["status"] == "enabled"
        ]
        files = sorted(files, key=lambda row: (row["order_index"], row["id"]))
        file_index = "none"
        for index, row in enumerate(files, start=1):
            if int(row["id"]) == file.id:
                file_index = index
                break
        pointer = int(data["queue_pointers"].get(f"{file.server}:{file.purpose}", 0))
        if not files:
            next_file_index: int | str = "none"
        elif pointer >= len(files):
            next_file_index = 1 if queue_mode == "cycle" else "none"
        else:
            next_file_index = pointer + 1
        return {
            "file_index": file_index,
            "file_total": len(files),
            "next_file_index": next_file_index,
        }

    def reset_queue(self, server: str, purpose: str) -> None:
        data = self._load()
        data["queue_pointers"][f"{server}:{purpose}"] = 0
        self._save(data)

    def set_file_status(self, file_id: int, status: str) -> bool:
        data = self._load()
        for row in data["account_files"]:
            if int(row["id"]) == file_id:
                row["status"] = status
                self._save(data)
                return True
        return False

    def move_file(self, file_id: int, order_index: int) -> bool:
        data = self._load()
        for row in data["account_files"]:
            if int(row["id"]) == file_id:
                row["order_index"] = order_index
                self._save(data)
                return True
        return False

    def delete_file(self, file_id: int) -> bool:
        data = self._load()
        kept = [row for row in data["account_files"] if int(row["id"]) != file_id]
        if len(kept) == len(data["account_files"]):
            return False
        deleted = next(row for row in data["account_files"] if int(row["id"]) == file_id)
        data["account_files"] = kept
        self._save(data)
        try:
            (self.uploads_dir / deleted["filename"]).unlink()
        except FileNotFoundError:
            pass
        return True

    def mark_generation(
        self,
        *,
        file_id: int | None,
        server: str,
        purpose: str,
        output_filename: str,
        failed_filename: str | None,
        success_count: int,
        failed_count: int,
    ) -> None:
        data = self._load()
        now = utc_now()
        data["generations"].append(
            {
                "id": self._next_id(data, "next_generation_id"),
                "file_id": file_id,
                "server": server,
                "purpose": purpose,
                "output_filename": output_filename,
                "failed_filename": failed_filename,
                "success_count": success_count,
                "failed_count": failed_count,
                "created_at": now,
            }
        )
        if file_id:
            for row in data["account_files"]:
                if int(row["id"]) == file_id:
                    row["last_used_at"] = now
                    break
        self._save(data)

    def record_last_generation(self, metadata: dict[str, Any]) -> None:
        data = self._load()
        key = f"{metadata['server']}:{metadata['purpose']}"
        data["last_generations"][key] = metadata
        self._save(data)

    def get_last_generation(self, server: str, purpose: str) -> dict[str, Any] | None:
        return self._load()["last_generations"].get(f"{server}:{purpose}")

    def status(self) -> dict[str, Any]:
        data = self._load()
        files = data["account_files"]
        pointers = []
        for key, value in sorted(data["queue_pointers"].items()):
            server, purpose = key.split(":", 1)
            pointers.append({"server": server, "purpose": purpose, "next_index": value})
        latest = data["generations"][-1] if data["generations"] else None
        return {
            "file_count": len(files),
            "account_count": sum(int(row["account_count"]) for row in files),
            "latest": latest,
            "pointers": pointers,
        }

    def add_schedule(self, server: str, purpose: str, mode: str, value: str, chat_id: int) -> int:
        data = self._load()
        schedule_id = self._next_id(data, "next_schedule_id")
        data["schedules"].append(
            {
                "id": schedule_id,
                "server": server,
                "purpose": purpose,
                "mode": mode,
                "value": value,
                "chat_id": chat_id,
                "enabled": True,
                "created_at": utc_now(),
            }
        )
        self._save(data)
        return schedule_id

    def list_schedules(self) -> list[dict[str, Any]]:
        return [
            dict(row)
            for row in self._load()["schedules"]
            if bool(row.get("enabled", True))
        ]

    def delete_schedule(self, schedule_id: int) -> bool:
        data = self._load()
        for row in data["schedules"]:
            if int(row["id"]) == schedule_id:
                row["enabled"] = False
                self._save(data)
                return True
        return False

    def _file_from_row(self, row: dict[str, Any]) -> AccountFile:
        return AccountFile(
            id=int(row["id"]),
            filename=str(row["filename"]),
            display_name=str(row["display_name"]),
            server=str(row["server"]),
            purpose=str(row["purpose"]),
            account_count=int(row["account_count"]),
            status=str(row["status"]),
            order_index=int(row["order_index"]),
            created_at=str(row["created_at"]),
            last_used_at=row.get("last_used_at"),
        )
