"""Configuration dataclasses and environment loading for inventory_monitor."""

import os
from dataclasses import dataclass, field
from typing import List, Optional

from dotenv import dotenv_values

from inventory_monitor.logger import logger

VALID_SNMP_VERSIONS = {2, 3}


@dataclass(frozen=True)
class SnmpV2Config:
    """SNMPv2 connection settings."""

    community: str = "public"


@dataclass(frozen=True)
class SnmpV3Config:
    """SNMPv3 connection settings."""

    user: str = ""
    auth_password: str = ""
    auth_protocol: Optional[str] = None  # "MD5" / "SHA"
    privacy_password: str = ""
    privacy_protocol: Optional[str] = None  # "DES" / "AES"
    security_level: str = "noAuthNoPriv"


@dataclass(frozen=True)
class SnmpConfig:
    """Combined SNMP configuration.

    Attributes:
        versions: List of SNMP versions to try in order (e.g., [3, 2]).
        v2: SNMPv2 configuration (community string).
        v3: SNMPv3 configuration (user, auth, privacy settings).
        timeout: SNMP timeout in seconds passed to easysnmp Session (default: 1 second).
        retries: Number of total attempts per SNMP version (1 = single attempt, no retry).
    """

    versions: List[int] = field(default_factory=lambda: [3, 2])
    v2: SnmpV2Config = field(default_factory=SnmpV2Config)
    v3: SnmpV3Config = field(default_factory=SnmpV3Config)
    timeout: int = 1
    retries: int = 1


@dataclass(frozen=True)
class NetBoxConfig:
    """NetBox API configuration."""

    api_token: str = ""
    instance_url: str = ""
    tags: List[str] = field(default_factory=list)
    tags_exclude: List[str] = field(default_factory=list)
    verify_ssl: bool = True


@dataclass(frozen=True)
class AppConfig:
    """Top-level application configuration."""

    snmp: SnmpConfig = field(default_factory=SnmpConfig)
    netbox: NetBoxConfig = field(default_factory=NetBoxConfig)
    parallel_jobs: int = 50
    log_level: str = "INFO"
    model_name_excludes: List[str] = field(default_factory=list)


def _parse_comma_list(value: Optional[str]) -> List[str]:
    """Split a comma-separated string into a list, stripping whitespace."""
    if not value:
        return []
    return [item.strip() for item in value.split(",") if item.strip()]


def _safe_int(value: Optional[str], default: int) -> int:
    """Parse an integer from string, returning default on failure."""
    if not value:
        return default
    try:
        return int(value)
    except (ValueError, TypeError):
        logger.warning(f"Invalid integer value '{value}', using default {default}")
        return default


def _parse_int_versions(value: Optional[str]) -> List[int]:
    """Parse comma-separated SNMP version string into list of valid ints (2 or 3)."""
    if not value:
        return [3, 2]
    versions = []
    for v in value.split(","):
        v = v.strip()
        if v:
            try:
                ver = int(v)
                if ver in VALID_SNMP_VERSIONS:
                    versions.append(ver)
                else:
                    logger.warning(f"Ignoring unsupported SNMP version: {ver}")
            except ValueError:
                logger.warning(f"Ignoring non-numeric SNMP version: '{v}'")
    return versions if versions else [3, 2]


def load_config_from_env(env: Optional[dict] = None, env_file: str = ".env") -> AppConfig:
    """Build AppConfig from env file -> os.environ.

    Args:
        env: Optional pre-loaded environment dict. If None, loads from env_file
             and os.environ. Pass a pre-built dict to avoid duplicate file reads.
        env_file: Path to .env file (default: ".env")

    Returns:
        Fully populated AppConfig instance.
    """
    if env is None:
        file_env = dotenv_values(env_file)
        if not file_env and not os.path.exists(env_file):
            logger.debug(f"Env file '{env_file}' not found, using environment variables and defaults")
        env = {
            **file_env,
            **os.environ,
        }

    # Accept both old "exludes" typo and corrected "excludes" spelling
    model_excludes_raw = env.get(
        "entPhysicalModelName_excludes",
        env.get("entPhysicalModelName_exludes", ""),
    )

    snmp_v2 = SnmpV2Config(
        community=env.get("SNMP_COMMUNITY", "public") or "public",
    )

    snmp_v3 = SnmpV3Config(
        user=env.get("SNMP_USER", "") or "",
        auth_password=env.get("SNMP_AUTH", "") or "",
        auth_protocol=env.get("SNMP_AUTH_METHOD") or None,
        privacy_password=env.get("SNMP_PRIVACY", "") or "",
        privacy_protocol=env.get("SNMP_PRIVACY_METHOD") or None,
        security_level=env.get("SNMP_SECURITY", "noAuthNoPriv") or "noAuthNoPriv",
    )

    snmp = SnmpConfig(
        versions=_parse_int_versions(env.get("SNMP_VERSION")),
        v2=snmp_v2,
        v3=snmp_v3,
        timeout=_safe_int(env.get("SNMP_TIMEOUT"), 1),
        retries=_safe_int(env.get("SNMP_RETRIES"), 1),
    )

    netbox = NetBoxConfig(
        api_token=env.get("NETBOX_API_TOKEN", "") or "",
        instance_url=env.get("NETBOX_INSTANCE_URL", "") or "",
        tags=_parse_comma_list(env.get("NETBOX_TAGS")),
        tags_exclude=_parse_comma_list(env.get("NETBOX_TAGS__N")),
        verify_ssl=(env.get("NETBOX_VERIFY_SSL") or "true").lower() != "false",
    )

    return AppConfig(
        snmp=snmp,
        netbox=netbox,
        parallel_jobs=_safe_int(env.get("PARALLEL_JOBS"), 50),
        log_level=(env.get("LOG_LEVEL", "INFO") or "INFO").upper(),
        model_name_excludes=_parse_comma_list(model_excludes_raw),
    )
