"""Local configuration file handling for a2a-agent."""

from __future__ import annotations

from dataclasses import dataclass
from dataclasses import field
import os
from pathlib import Path
import platform
from typing import Any

import yaml


CONFIG_PATH = Path.home() / ".a2a-agent" / "config.yaml"
DEFAULT_ROOM = os.environ.get("A2A_ROOM", "team")
# 공개 배포되는 패키지에는 특정 브로커 주소를 박지 않는다.
# 실제 브로커는 비공개 install.sh나 환경변수(A2A_BROKER_HOST/PORT)로 주입한다.
DEFAULT_BROKER_HOST = os.environ.get("A2A_BROKER_HOST", "localhost")
DEFAULT_BROKER_PORT = int(os.environ.get("A2A_BROKER_PORT", "1883"))
DEFAULT_EXECUTOR = "echo"
# 공개 배포되는 패키지에 특정 NAS 주소를 박지 않는다. 실제 주소는 A2A_SHARE_HOST로 주입.
DEFAULT_SHARE_HOST = os.environ.get("A2A_SHARE_HOST", "")
DEFAULT_SHARE_PORT = 5001
DEFAULT_SHARE_FOLDER = "/a2a-share"
VALID_ROLES = {"commander", "worker"}


@dataclass(frozen=True)
class BrokerConfig:
    host: str = DEFAULT_BROKER_HOST
    port: int = DEFAULT_BROKER_PORT
    tls: bool = False

    def to_dict(self) -> dict[str, Any]:
        return {"host": self.host, "port": self.port, "tls": self.tls}

    @classmethod
    def from_dict(cls, data: dict[str, Any] | None) -> "BrokerConfig":
        data = data or {}
        if not isinstance(data, dict):
            raise ValueError("broker must be a mapping")
        return cls(
            host=str(data.get("host", DEFAULT_BROKER_HOST)),
            port=int(data.get("port", DEFAULT_BROKER_PORT)),
            tls=_bool_from_config(data.get("tls", False), "broker.tls"),
        )


@dataclass(frozen=True)
class ShareConfig:
    host: str = DEFAULT_SHARE_HOST
    port: int = DEFAULT_SHARE_PORT
    user: str = ""
    password: str = ""
    folder: str = DEFAULT_SHARE_FOLDER
    insecure: bool = False

    def __post_init__(self) -> None:
        clean_folder = self.folder.strip("/")
        if not clean_folder:
            raise ValueError("share.folder must not be empty")
        folder = "/" + clean_folder
        object.__setattr__(self, "folder", folder)

    def to_dict(self) -> dict[str, Any]:
        return {
            "host": self.host,
            "port": self.port,
            "user": self.user,
            "password": self.password,
            "folder": self.folder,
            "insecure": self.insecure,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any] | None) -> "ShareConfig":
        data = data or {}
        if not isinstance(data, dict):
            raise ValueError("share must be a mapping")
        return cls(
            host=str(data.get("host", DEFAULT_SHARE_HOST)),
            port=int(data.get("port", DEFAULT_SHARE_PORT)),
            user=str(data.get("user", "")),
            password=str(data.get("password", "")),
            folder=str(data.get("folder", DEFAULT_SHARE_FOLDER)),
            insecure=_bool_from_config(data.get("insecure", False), "share.insecure"),
        )


@dataclass(frozen=True)
class DeviceRole:
    role: str
    description: str = ""

    def to_dict(self) -> dict[str, Any]:
        return {"role": self.role, "description": self.description}

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "DeviceRole":
        return cls(
            role=str(data.get("role", "")),
            description=str(data.get("description", "")),
        )


@dataclass(frozen=True)
class PendingPairing:
    device_name: str
    room: str
    expires_at: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "device_name": self.device_name,
            "room": self.room,
            "expires_at": self.expires_at,
        }

    @classmethod
    def from_dict(cls, data: dict[str, Any]) -> "PendingPairing":
        return cls(
            device_name=str(data.get("device_name", "")),
            room=str(data.get("room", DEFAULT_ROOM)),
            expires_at=str(data.get("expires_at", "")),
        )


@dataclass(frozen=True)
class AgentConfig:
    room: str = DEFAULT_ROOM
    device_name: str = platform.node() or "device"
    broker: BrokerConfig = BrokerConfig()
    role: str = "worker"
    token: str | None = None
    devices: dict[str, DeviceRole] = field(default_factory=dict)
    aliases: dict[str, str] = field(default_factory=dict)
    pending_pairings: dict[str, PendingPairing] = field(default_factory=dict)
    executor: str = DEFAULT_EXECUTOR
    share: ShareConfig | None = None
    cursor_api_key: str | None = None
    ollama_api_key: str | None = None
    ollama_model: str | None = None

    def __post_init__(self) -> None:
        if self.role not in VALID_ROLES:
            allowed = ", ".join(sorted(VALID_ROLES))
            raise ValueError(f"role must be one of: {allowed}")

    def to_dict(self) -> dict[str, Any]:
        data = {
            "room": self.room,
            "device_name": self.device_name,
            "broker": self.broker.to_dict(),
            "role": self.role,
            "executor": self.executor,
            "aliases": dict(sorted(self.aliases.items())),
            "devices": {
                name: role.to_dict()
                for name, role in sorted(self.devices.items())
            },
            "pending_pairings": {
                code: pairing.to_dict()
                for code, pairing in sorted(self.pending_pairings.items())
            },
        }
        if self.token is not None:
            data["token"] = self.token
        if self.cursor_api_key is not None:
            data["cursor_api_key"] = self.cursor_api_key
        if self.ollama_api_key is not None:
            data["ollama_api_key"] = self.ollama_api_key
        if self.ollama_model is not None:
            data["ollama_model"] = self.ollama_model
        if self.share is not None:
            data["share"] = self.share.to_dict()
        return data

    @classmethod
    def from_dict(cls, data: dict[str, Any] | None) -> "AgentConfig":
        data = data or {}
        if not isinstance(data, dict):
            raise ValueError("config must be a mapping")
        devices = data.get("devices", {})
        if devices is None:
            devices = {}
        if not isinstance(devices, dict):
            raise ValueError("devices must be a mapping")
        aliases = data.get("aliases", {})
        if aliases is None:
            aliases = {}
        if not isinstance(aliases, dict):
            raise ValueError("aliases must be a mapping")
        pending_pairings = data.get("pending_pairings", {})
        if pending_pairings is None:
            pending_pairings = {}
        if not isinstance(pending_pairings, dict):
            raise ValueError("pending_pairings must be a mapping")

        return cls(
            room=str(data.get("room", DEFAULT_ROOM)),
            device_name=str(data.get("device_name", platform.node() or "device")),
            broker=BrokerConfig.from_dict(data.get("broker")),
            role=str(data.get("role", "worker")),
            token=data.get("token"),
            devices={
                str(name): DeviceRole.from_dict(_role_data(role_data))
                for name, role_data in devices.items()
            },
            aliases={
                str(alias): str(device_name)
                for alias, device_name in aliases.items()
            },
            pending_pairings={
                str(code): PendingPairing.from_dict(_pairing_data(pairing_data))
                for code, pairing_data in pending_pairings.items()
            },
            executor=str(data.get("executor", DEFAULT_EXECUTOR)),
            cursor_api_key=(
                str(data["cursor_api_key"])
                if data.get("cursor_api_key") is not None
                else None
            ),
            ollama_api_key=(
                str(data["ollama_api_key"])
                if data.get("ollama_api_key") is not None
                else None
            ),
            ollama_model=(
                str(data["ollama_model"])
                if data.get("ollama_model") is not None
                else None
            ),
            share=(
                ShareConfig.from_dict(data.get("share"))
                if data.get("share") is not None
                else None
            ),
        )


def share_config_from_env() -> ShareConfig | None:
    user = os.environ.get("A2A_SHARE_USER")
    password = os.environ.get("A2A_SHARE_PASSWORD")
    if not user and not password:
        return None
    if not user or not password:
        raise ValueError("A2A_SHARE_USER and A2A_SHARE_PASSWORD must be set together")

    return ShareConfig(
        host=os.environ.get("A2A_SHARE_HOST", DEFAULT_SHARE_HOST),
        port=int(os.environ.get("A2A_SHARE_PORT", str(DEFAULT_SHARE_PORT))),
        user=user,
        password=password,
        folder=os.environ.get("A2A_SHARE_FOLDER", DEFAULT_SHARE_FOLDER),
        insecure=_bool_from_config(
            os.environ.get("A2A_SHARE_INSECURE", "false"),
            "A2A_SHARE_INSECURE",
        ),
    )


def default_config_path() -> Path:
    home = os.environ.get("A2A_HOME")
    if home:
        return Path(home) / "config.yaml"
    return CONFIG_PATH


def load_config(path: Path | None = None) -> AgentConfig:
    config_path = path or default_config_path()
    if not config_path.exists():
        return AgentConfig()

    raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {}
    return AgentConfig.from_dict(raw)


def save_config(config: AgentConfig, path: Path | None = None) -> Path:
    config_path = path or default_config_path()
    config_path.parent.mkdir(parents=True, exist_ok=True)
    payload = yaml.safe_dump(config.to_dict(), allow_unicode=True, sort_keys=True)
    tmp_path = config_path.with_name(f".{config_path.name}.{os.getpid()}.tmp")

    fd = os.open(tmp_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
    try:
        with os.fdopen(fd, "w", encoding="utf-8") as file:
            file.write(payload)
        os.replace(tmp_path, config_path)
    finally:
        if tmp_path.exists():
            tmp_path.unlink()
    config_path.chmod(0o600)
    return config_path


def _role_data(data: Any) -> dict[str, Any]:
    if data is None:
        return {}
    if not isinstance(data, dict):
        raise ValueError("device role entry must be a mapping")
    return data


def _pairing_data(data: Any) -> dict[str, Any]:
    if data is None:
        return {}
    if not isinstance(data, dict):
        raise ValueError("pending pairing entry must be a mapping")
    return data


def _bool_from_config(value: Any, name: str) -> bool:
    if isinstance(value, bool):
        return value
    if isinstance(value, str):
        lowered = value.lower()
        if lowered in {"true", "yes", "1"}:
            return True
        if lowered in {"false", "no", "0"}:
            return False
    raise ValueError(f"{name} must be a boolean")
