"""Data models, constants, and the built-in CPU spec map."""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any


# ─── Feed constants ───────────────────────────────────────────────────────────

DEFAULT_FEED_CURRENCY = "USD"
FEED_URLS: dict[str, str] = {
    "EUR": "https://www.hetzner.com/_resources/app/data/app/live_data_sb_EUR.json",
    "USD": "https://www.hetzner.com/_resources/app/data/app/live_data_sb_USD.json",
}
DEFAULT_FEED_URL = FEED_URLS[DEFAULT_FEED_CURRENCY]

DEFAULT_GPU_HINTS: tuple[str, ...] = (
    "GPU", "NVIDIA", "RTX", "TESLA",
    "A100", "A6000", "H100", "L40", "L4", "V100",
)

SORT_KEYS: dict[str, str] = {
    "raw-cpu": "raw_cpu_value",
    "raw-ram": "raw_ram_value",
    "raw-storage": "raw_storage_value",
    "cpu-rank": "score_cpu_rank",
    "storage-rank": "score_storage_rank",
    "overall": "score_overall_rank",
}

EXPORT_FIELDS: tuple[str, ...] = (
    "id",
    "cpu",
    "cores",
    "threads",
    "ram_gb",
    "price_amount",
    "price_currency",
    "storage_gb",
    "storage_tb",
    "nvme_count",
    "sata_count",
    "ssd_count",
    "hdd_count",
    "nvme_gb",
    "sata_gb",
    "hdd_gb",
    "has_ssd",
    "has_hdd",
    "datacenter",
    "bandwidth_mbit",
    "ecc",
    "has_inic",
    "has_gpu",
    "bonus_multiplier",
    "raw_cpu_value",
    "raw_ram_value",
    "raw_storage_value",
    "score_cpu_rank",
    "score_storage_rank",
    "score_overall_rank",
    "drives",
    "specials",
)


# ─── Built-in CPU model map ──────────────────────────────────────────────────
# Extend via [cpu_specs] in your config file.

BUILTIN_CPU_SPECS: dict[str, dict[str, int]] = {
    "AMD EPYC 7401P": {"cores": 24, "threads": 48},
    "AMD EPYC 7502": {"cores": 32, "threads": 64},
    "AMD EPYC 7502P": {"cores": 32, "threads": 64},
    "AMD Ryzen 5 3600": {"cores": 6, "threads": 12},
    "AMD Ryzen 7 1700X": {"cores": 8, "threads": 16},
    "AMD Ryzen 7 3700X": {"cores": 8, "threads": 16},
    "AMD Ryzen 7 7700": {"cores": 8, "threads": 16},
    "AMD Ryzen 7 PRO 1700X": {"cores": 8, "threads": 16},
    "AMD Ryzen 9 3900": {"cores": 12, "threads": 24},
    "AMD Ryzen 9 5950X": {"cores": 16, "threads": 32},
    "AMD Ryzen Threadripper 2950X": {"cores": 16, "threads": 32},
    "Intel Core i5-12500": {"cores": 6, "threads": 12},
    "Intel Core i7-6700": {"cores": 4, "threads": 8},
    "Intel Core i7-7700": {"cores": 4, "threads": 8},
    "Intel Core i7-8700": {"cores": 6, "threads": 12},
    "Intel Core i9-12900K": {"cores": 16, "threads": 24},
    "Intel Core i9-13900": {"cores": 24, "threads": 32},
    "Intel Core i9-9900K": {"cores": 8, "threads": 16},
    "Intel XEON E-2176G": {"cores": 6, "threads": 12},
    "Intel XEON E-2276G": {"cores": 6, "threads": 12},
    "Intel Xeon E3-1270V3": {"cores": 4, "threads": 8},
    "Intel Xeon E3-1271V3": {"cores": 4, "threads": 8},
    "Intel Xeon E3-1275V6": {"cores": 4, "threads": 8},
    "Intel Xeon E3-1275v5": {"cores": 4, "threads": 8},
    "Intel Xeon E5-1650V3": {"cores": 6, "threads": 12},
    "Intel Xeon Gold 5412U": {"cores": 24, "threads": 48},
    "Intel Xeon W-2145": {"cores": 8, "threads": 16},
    "Intel Xeon W-2245": {"cores": 8, "threads": 16},
    "Intel Xeon W-2295": {"cores": 18, "threads": 36},
}


# ─── Dataclasses ──────────────────────────────────────────────────────────────


@dataclass(frozen=True)
class DiskRequirements:
    min_ssd_drives: int = 0
    min_hdd_drives: int = 0
    ssd_only: bool = False


@dataclass(frozen=True)
class BonusConfig:
    inic_bonus: float = 0.10
    gpu_bonus: float = 0.15


@dataclass(frozen=True)
class ScoreWeights:
    cpu_rank_compute: float = 0.75
    storage_rank_storage: float = 0.75
    overall_compute: float = 0.40
    overall_ram: float = 0.30
    overall_storage: float = 0.30

    @property
    def cpu_rank_ram(self) -> float:
        return 1.0 - self.cpu_rank_compute

    @property
    def storage_rank_ram(self) -> float:
        return 1.0 - self.storage_rank_storage


@dataclass(frozen=True)
class FilterConfig:
    ecc_only: bool = True
    price_cap: float | None = None
    min_ram_gb: int = 0
    max_ram_gb: int | None = None
    min_storage_gb: int = 0
    max_storage_gb: int | None = None
    disk: DiskRequirements = field(default_factory=DiskRequirements)
    datacenter: str | None = None
    exclude_datacenter: str | None = None
    cpu_regex_raw: str | None = None
    exclude_cpu_regex_raw: str | None = None
    cpu_regex: re.Pattern[str] | None = None
    exclude_cpu_regex: re.Pattern[str] | None = None
    only_gpu: bool = False
    price_per_thread_cap: float | None = None


@dataclass(frozen=True)
class AlertConfig:
    """Threshold-based alerting.

    If any scored server exceeds a threshold the alert fires and can optionally:
    - run a shell command with the alert payload on stdin
    - send an email via local ``msmtp``
    """
    min_overall_score: float | None = None
    min_cpu_score: float | None = None
    min_storage_score: float | None = None
    max_price: float | None = None
    notify_command: str | None = None
    email_to: tuple[str, ...] = ()
    email_from: str | None = None
    email_subject: str = "sb-scout alert"
    email_account: str | None = None


@dataclass(frozen=True)
class AppConfig:
    url: str = DEFAULT_FEED_URL
    currency: str = DEFAULT_FEED_CURRENCY
    top: int = 5
    output_format: str = "text"
    out_dir: Path = field(default_factory=lambda: Path("/tmp/sb-scout"))
    cache_dir: Path | None = None
    csv_out: str | None = None
    json_out: str | None = None
    no_write: bool = False
    strict_cpu_map: bool = False
    save_markdown: str | None = None
    sort_by: str = "overall"
    profiles: tuple[str, ...] = ()
    profile: str | None = None
    bonuses: BonusConfig = field(default_factory=BonusConfig)
    weights: ScoreWeights = field(default_factory=ScoreWeights)
    filters: FilterConfig = field(default_factory=FilterConfig)
    alerts: AlertConfig = field(default_factory=AlertConfig)
    gpu_hints: tuple[str, ...] = DEFAULT_GPU_HINTS
    history_file: str | None = None
    watch_interval: int | None = None
    json_stdout: bool = False
