from __future__ import annotations

import argparse
from dataclasses import asdict, dataclass
import json
from pathlib import Path
import re
from typing import Any, Literal

from src.ctb_copy.runtime_paths import CopyResearchPaths

ADDRESS_RE = re.compile(r"^0x[a-fA-F0-9]{40}$")


@dataclass(frozen=True)
class WatchlistEntry:
    address: str
    label: str = ""
    source: str = "manual"
    enabled: bool = True
    notes: str = ""


@dataclass(frozen=True)
class VaultWatchlistEntry:
    vault_address: str
    label: str = ""
    source: str = "manual"
    enabled: bool = True
    notes: str = ""


@dataclass(frozen=True)
class WatchlistValidation:
    ok: bool
    errors: tuple[str, ...]
    warnings: tuple[str, ...] = ()


def default_watchlist() -> dict[str, Any]:
    return {"version": 1, "wallets": [], "vaults": []}


def watchlist_path(runtime_dir: str | Path | None = None) -> Path:
    return CopyResearchPaths(Path(runtime_dir) if runtime_dir else CopyResearchPaths().root).watchlist_file


def init_watchlist(path: str | Path | None = None, *, overwrite: bool = False) -> Path:
    target = Path(path) if path else watchlist_path()
    target.parent.mkdir(parents=True, exist_ok=True)
    if target.exists() and not overwrite:
        return target
    target.write_text(json.dumps(default_watchlist(), indent=2, sort_keys=True) + "\n", encoding="utf-8")
    return target


def load_watchlist(path: str | Path | None = None, *, create_if_missing: bool = False) -> dict[str, Any]:
    target = Path(path) if path else watchlist_path()
    if not target.exists():
        if create_if_missing:
            init_watchlist(target)
        else:
            return default_watchlist()
    payload = json.loads(target.read_text(encoding="utf-8"))
    if not isinstance(payload, dict):
        raise ValueError("watchlist payload must be an object")
    return payload


def save_watchlist(payload: dict[str, Any], path: str | Path | None = None) -> Path:
    validation = validate_watchlist(payload)
    if not validation.ok:
        raise ValueError("invalid watchlist: " + "; ".join(validation.errors))
    target = Path(path) if path else watchlist_path()
    target.parent.mkdir(parents=True, exist_ok=True)
    target.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8")
    return target


def validate_watchlist(payload: dict[str, Any]) -> WatchlistValidation:
    errors: list[str] = []
    warnings: list[str] = []
    if payload.get("version") != 1:
        errors.append("version_must_be_1")
    for key in ("wallets", "vaults"):
        if not isinstance(payload.get(key, []), list):
            errors.append(f"{key}_must_be_list")
    for idx, row in enumerate(payload.get("wallets", [])):
        if not isinstance(row, dict):
            errors.append(f"wallets[{idx}]_must_be_object")
            continue
        address = str(row.get("address", ""))
        if not ADDRESS_RE.match(address):
            errors.append(f"wallets[{idx}].address_invalid")
        if not isinstance(row.get("enabled", True), bool):
            errors.append(f"wallets[{idx}].enabled_must_be_bool")
    for idx, row in enumerate(payload.get("vaults", [])):
        if not isinstance(row, dict):
            errors.append(f"vaults[{idx}]_must_be_object")
            continue
        address = str(row.get("vault_address", ""))
        if not ADDRESS_RE.match(address):
            errors.append(f"vaults[{idx}].vault_address_invalid")
        if not isinstance(row.get("enabled", True), bool):
            errors.append(f"vaults[{idx}].enabled_must_be_bool")
    return WatchlistValidation(ok=not errors, errors=tuple(errors), warnings=tuple(warnings))


def _valid_enabled_rows(payload: dict[str, Any], section: Literal["wallets", "vaults"]) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    address_key = "address" if section == "wallets" else "vault_address"
    for row in payload.get(section, []):
        if not isinstance(row, dict) or row.get("enabled", True) is not True:
            continue
        address = str(row.get(address_key, ""))
        if ADDRESS_RE.match(address):
            rows.append(row)
    return rows


def enabled_wallets(payload: dict[str, Any] | None = None) -> tuple[WatchlistEntry, ...]:
    source = payload if payload is not None else load_watchlist()
    return tuple(WatchlistEntry(address=str(row["address"]), label=str(row.get("label", "")), source=str(row.get("source", "manual")), enabled=True, notes=str(row.get("notes", ""))) for row in _valid_enabled_rows(source, "wallets"))


def enabled_vaults(payload: dict[str, Any] | None = None) -> tuple[VaultWatchlistEntry, ...]:
    source = payload if payload is not None else load_watchlist()
    return tuple(VaultWatchlistEntry(vault_address=str(row["vault_address"]), label=str(row.get("label", "")), source=str(row.get("source", "manual")), enabled=True, notes=str(row.get("notes", ""))) for row in _valid_enabled_rows(source, "vaults"))


def watchlist_summary(payload: dict[str, Any], *, exists: bool = False) -> dict[str, Any]:
    validation = validate_watchlist(payload)
    wallets = enabled_wallets(payload) if validation.ok else ()
    vaults = enabled_vaults(payload) if validation.ok else ()
    return {
        "watchlist_exists": exists,
        "validation_ok": validation.ok,
        "validation_errors": list(validation.errors),
        "enabled_wallets_count": len(wallets),
        "enabled_vaults_count": len(vaults),
        "wallet_labels": [wallet.label for wallet in wallets],
        "wallet_sources": sorted({wallet.source for wallet in wallets}),
        "read_only_observation_only": all("read-only observation only" in wallet.notes for wallet in wallets),
        "copy_permission": False,
        "wallets_total": len(payload.get("wallets", [])) if isinstance(payload.get("wallets", []), list) else 0,
        "vaults_total": len(payload.get("vaults", [])) if isinstance(payload.get("vaults", []), list) else 0,
    }


def main(argv: list[str] | None = None) -> int:
    parser = argparse.ArgumentParser(description="Manage the read-only copy-research runtime watchlist.")
    parser.add_argument("--runtime-dir", default=str(CopyResearchPaths().root))
    parser.add_argument("--init", action="store_true", help="Create an empty runtime watchlist if missing.")
    parser.add_argument("--show", action="store_true", help="Print watchlist summary and payload.")
    args = parser.parse_args(argv)
    path = watchlist_path(args.runtime_dir)
    if args.init:
        init_watchlist(path)
    if args.show or not args.init:
        payload = load_watchlist(path)
        print(json.dumps({"path": str(path), "summary": watchlist_summary(payload, exists=path.exists()), "payload": payload}, indent=2, sort_keys=True))
    else:
        print(path)
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
