import json
from dataclasses import dataclass, field
from typing import Any, Dict, Optional

from .iconfig import IBaseConfig

# from iconfig import IBaseConfig


@dataclass
class Target:
    """
    Class to capture Single Target Log Handler
    """

    enabled: bool = False
    options: Dict[str, Any] = field(default_factory=dict)
    handlers: Dict[str, Any] = field(default_factory=dict)

    @staticmethod
    def from_dict(d: Dict[str, Any]) -> "Target":
        return Target(
            enabled=d.get("enabled", False),
            options=dict(d.get("options", {}) or {}),
            handlers=dict(d.get("handlers", {}) or {}),
        )

    def to_dict(self) -> Dict[str, Any]:
        return {
            "enabled": self.enabled,
            "options": dict(self.options),
            "handlers": dict(self.handlers),
        }


class LoggerConfig(IBaseConfig):
    """
    Holds a dictionary of named targets. You can add/remove/update targets programmatically.
    """

    def __init__(self, targets: Optional[Dict[str, Target]] = None):
        # keep targets as a dict so callers can add arbitrary target names at runtime
        self.targets: Dict[str, Target] = dict(targets or {})

    # --------- Construction / factories ----------
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> "LoggerConfig":
        raw_targets = data.get("targets", {}) if isinstance(data, dict) else {}
        targets = {name: Target.from_dict(t) for name, t in raw_targets.items()}
        return cls(targets=targets)

    def to_dict(self) -> Dict[str, Any]:
        return {"targets": {name: t.to_dict() for name, t in self.targets.items()}}

    def to_json(self, **json_kwargs) -> str:
        kwargs = {"indent": 4, "ensure_ascii": False}
        kwargs.update(json_kwargs)
        return json.dumps(self.to_dict(), **kwargs)

    # --------- Dynamic modification API ----------
    def add_target(
        self, name: str, target: Optional[Target] = None, *, overwrite: bool = False
    ) -> Target:
        """Add a new target by name. Returns the Target object stored."""
        if name in self.targets and not overwrite:
            raise KeyError(
                f"Target '{name}' already exists. Use overwrite=True to replace."
            )
        self.targets[name] = target or Target()
        return self.targets[name]

    def remove_target(self, name: str) -> None:
        """Remove a target. Raises KeyError if missing."""
        del self.targets[name]

    def get_target(self, name: str) -> Target:
        """Return target object (for in-place modification). Raises KeyError if missing."""
        return self.targets[name]

    def update_target(
        self,
        name: str,
        *,
        enabled: Optional[bool] = None,
        options: Optional[Dict[str, Any]] = None,
        handlers: Optional[Dict[str, Any]] = None,
    ) -> Target:
        """Partial update of a target (creates target if missing)."""
        if name not in self.targets:
            self.targets[name] = Target()
        t = self.targets[name]
        if enabled is not None:
            t.enabled = enabled
        if options is not None:
            # shallow merge so you can update specific keys
            t.options.update(options)
        if handlers is not None:
            t.handlers.update(handlers)
        return t

    def enable_target(self, name: str) -> None:
        self.update_target(name, enabled=True)

    def disable_target(self, name: str) -> None:
        self.update_target(name, enabled=False)

    def list_targets(self) -> Dict[str, Target]:
        """Return copy of target dict reference (useful for inspection)."""
        return dict(self.targets)

    def load(self) -> dict:
        return self.to_dict()

    # --------- Convenience factories for common targets ----------
    @staticmethod
    def make_console(enabled: bool = True) -> Target:
        return Target(enabled=enabled, options={}, handlers={})

    @staticmethod
    def make_file(
        log_dir: str = "./logs",
        filename: str = "app.log",
        rotation: str = "daily",
        backup_count: int = 7,
        enabled: bool = True,
    ) -> Target:
        return Target(
            enabled=enabled,
            options={},
            handlers={
                "log_dir": log_dir,
                "filename": filename,
                "rotation": rotation,
                "backup_count": backup_count,
            },
        )

    @staticmethod
    def make_opensearch(
        host: str = "http://localhost",
        port: int = 9200,
        user: str = "",
        password: str = "",
        index: str = "",
        enabled: bool = False,
    ) -> Target:
        return Target(
            enabled=enabled,
            options={"host": host, "port": port, "user": user, "password": password},
            handlers={"index": index} if index else {},
        )
