"""Local configuration and state management."""

from __future__ import annotations

import json
import os
from dataclasses import asdict, dataclass, field
from pathlib import Path

from .exceptions import BeaconConfigError

# Paths

STATE_DIR = Path(os.environ.get("ETHIACK_STATE_DIR", Path.home() / ".ethiack"))
STATE_FILE = STATE_DIR / "state.json"
RATHOLE_DIR = STATE_DIR / "rathole"
PIDS_DIR = STATE_DIR / "pids"
WG_DIR = STATE_DIR / "wireguard"
DNS_DIR = STATE_DIR / "dns"

DEFAULT_API_URL = "https://api.ethiack.com"
DEFAULT_DNS_SERVER = "10.252.0.1"

# API settings (read from env vars)

@dataclass
class Settings:
    api_url: str = DEFAULT_API_URL
    api_key: str = ""
    api_secret: str = ""
    org_id: str = ""

    @classmethod
    def from_env(cls) -> "Settings":
        return cls(
            api_url=os.environ.get("ETHIACK_API_URL", DEFAULT_API_URL).rstrip("/"),
            api_key=os.environ.get("ETHIACK_API_KEY", ""),
            api_secret=os.environ.get("ETHIACK_API_SECRET", ""),
            org_id=os.environ.get("ETHIACK_ORG_ID", ""),
        )

    def validate(self) -> None:
        missing = []
        if not self.api_key:
            missing.append("ETHIACK_API_KEY")
        if not self.api_secret:
            missing.append("ETHIACK_API_SECRET")
        if missing:
            raise BeaconConfigError(
                f"Missing required environment variables: {', '.join(missing)}"
            )


# Local state persisted across invocations

@dataclass
class LocalBeacon:
    """Runtime record for a beacon managed on this machine."""

    beacon_id: str
    name: str
    interface: str
    wg_port: int
    rathole_config_path: str = ""
    # PID is stored in a separate file; -1 means not running
    rathole_pid: int = -1
    # Runtime credentials - populated after create so start/health work without API key/secret
    beacon_token: str = ""
    rathole_server: str = ""
    rathole_control_port: int = 0
    rathole_public_port: int = 0
    gateway_ip: str = ""  # WireGuard server address (e.g. 10.252.0.1) for DNS forwarder


@dataclass
class LocalState:
    beacons: dict[str, LocalBeacon] = field(default_factory=dict)

    # Persistence

    @classmethod
    def load(cls) -> "LocalState":
        _ensure_dirs()
        if not STATE_FILE.exists():
            return cls()
        try:
            raw = json.loads(STATE_FILE.read_text())
            beacons = {
                bid: LocalBeacon(**data)
                for bid, data in raw.get("beacons", {}).items()
            }
            return cls(beacons=beacons)
        except Exception as exc:
            raise BeaconConfigError(f"Corrupt state file at {STATE_FILE}: {exc}") from exc

    def save(self) -> None:
        _ensure_dirs()
        data = {"beacons": {bid: asdict(b) for bid, b in self.beacons.items()}}
        STATE_FILE.write_text(json.dumps(data, indent=2))

    # Helpers

    def add(self, beacon: LocalBeacon) -> None:
        self.beacons[beacon.beacon_id] = beacon
        self.save()

    def remove(self, beacon_id: str) -> None:
        self.beacons.pop(beacon_id, None)
        self.save()

    def get(self, beacon_id: str) -> LocalBeacon | None:
        return self.beacons.get(beacon_id)

    def update_pid(self, beacon_id: str, pid: int) -> None:
        if beacon_id in self.beacons:
            self.beacons[beacon_id].rathole_pid = pid
            self.save()


# PID file helpers (rathole process)

def write_pid(beacon_id: str, pid: int) -> None:
    _ensure_dirs()
    pid_file = PIDS_DIR / f"{beacon_id}.pid"
    pid_file.write_text(str(pid))


def read_pid(beacon_id: str) -> int | None:
    pid_file = PIDS_DIR / f"{beacon_id}.pid"
    if not pid_file.exists():
        return None
    try:
        return int(pid_file.read_text().strip())
    except ValueError:
        return None


def clear_pid(beacon_id: str) -> None:
    pid_file = PIDS_DIR / f"{beacon_id}.pid"
    pid_file.unlink(missing_ok=True)


# Internal


def _ensure_dirs() -> None:
    for d in (STATE_DIR, RATHOLE_DIR, PIDS_DIR, WG_DIR, DNS_DIR):
        d.mkdir(parents=True, exist_ok=True)
