from __future__ import annotations

import os
import sys
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, Optional

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
DEFAULT_HOST = "localhost"
DEFAULT_PORT = 5678
DEFAULT_CHECKPOINT_PATH = ".pulselog/checkpoints.db"
DEFAULT_LEVEL = "DEBUG"
DEFAULT_WORKER_INTERVAL = 0.01
PORT_MIN = 1024
PORT_MAX = 65535

_TRUTHY = {"true", "1", "yes"}
_FALSY = {"false", "0", "no"}


def _parse_bool(val: str) -> bool:
    """Parse an environment-variable boolean string."""
    low = val.lower().strip()
    if low in _TRUTHY:
        return True
    if low in _FALSY:
        return False
    raise ValueError(f"Cannot parse {val!r} as bool; use true/false/1/0/yes/no")


@dataclass
class Config:
    """Resolved runtime configuration for a Logger instance.

    Fields match the Logger() constructor kwargs one-for-one.
    """

    name: str = "pulselog"
    host: str = DEFAULT_HOST
    port: int = DEFAULT_PORT
    auto_open: bool = True
    dashboard: bool = True
    checkpoint_path: str = DEFAULT_CHECKPOINT_PATH
    level: str = DEFAULT_LEVEL
    worker_interval: float = DEFAULT_WORKER_INTERVAL

    # Derived — set during load()
    level_value: int = field(default=10, init=False)

    def __post_init__(self) -> None:
        from pulselog.levels import Level  # local import to avoid circular
        self.level_value = Level.from_str(self.level).value
        self._validate()

    def _validate(self) -> None:
        if not (PORT_MIN <= self.port <= PORT_MAX):
            raise ValueError(
                f"Port must be between {PORT_MIN} and {PORT_MAX}, got {self.port}"
            )

    @classmethod
    def load(cls, **kwargs: Any) -> "Config":
        """Build a Config by merging all configuration sources.

        Priority (highest → lowest):
            1. kwargs passed directly to Logger()
            2. PULSELOG_* environment variables
            3. pulselog.toml in the current working directory
            4. Dataclass defaults

        Args:
            **kwargs: Direct overrides; None values are ignored.

        Returns:
            Fully resolved Config instance.
        """
        merged: Dict[str, Any] = {}

        # --- Layer 3: pulselog.toml ---
        toml_path = Path.cwd() / "pulselog.toml"
        if toml_path.exists():
            try:
                if sys.version_info >= (3, 11):
                    import tomllib
                    with toml_path.open("rb") as fh:
                        raw = tomllib.load(fh)
                else:
                    import tomli  # type: ignore[import]
                    with toml_path.open("rb") as fh:
                        raw = tomli.load(fh)
                merged.update(raw.get("pulselog", {}))
            except Exception as exc:
                sys.stderr.write(f"[pulselog] Warning: failed to read pulselog.toml: {exc}\n")

        # --- Layer 2: environment variables ---
        env_map: Dict[str, str] = {
            "PULSELOG_HOST": "host",
            "PULSELOG_PORT": "port",
            "PULSELOG_AUTO_OPEN": "auto_open",
            "PULSELOG_DASHBOARD": "dashboard",
            "PULSELOG_CHECKPOINT_PATH": "checkpoint_path",
            "PULSELOG_LEVEL": "level",
            "PULSELOG_WORKER_INTERVAL": "worker_interval",
        }
        for env_key, field_name in env_map.items():
            raw_val = os.environ.get(env_key)
            if raw_val is None:
                continue
            if field_name in ("auto_open", "dashboard"):
                merged[field_name] = _parse_bool(raw_val)
            elif field_name == "port":
                merged[field_name] = int(raw_val)
            elif field_name == "worker_interval":
                merged[field_name] = float(raw_val)
            else:
                merged[field_name] = raw_val

        # --- Layer 1: direct kwargs (skip None) ---
        for k, v in kwargs.items():
            if v is not None:
                merged[k] = v

        # CI / dumb-terminal environment detection
        if merged.get("auto_open", True):
            if os.environ.get("CI", "").lower() in _TRUTHY:
                merged["auto_open"] = False
            elif os.environ.get("TERM", "") == "dumb":
                merged["auto_open"] = False

        return cls(**{k: v for k, v in merged.items() if k != "level_value"})
