from __future__ import annotations

import json
import logging
import math
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, Iterable, Tuple

CONFIG_FILENAME = ".avraels.json"
log = logging.getLogger(__name__)


class ConfigError(Exception):
    """Raised when a workspace config file cannot be parsed."""


@dataclass
class DiagnosticSettings:
    semantic_level: str = "warning"
    runtime_level: str = "error"


@dataclass
class AvraeServiceConfig:
    base_url: str = "https://api.avrae.io"
    token: str | None = None


@dataclass
class VarSources:
    cvars: Dict[str, Any] = field(default_factory=dict)
    uvars: Dict[str, Any] = field(default_factory=dict)
    svars: Dict[str, Any] = field(default_factory=dict)
    gvars: Dict[str, Any] = field(default_factory=dict)

    @classmethod
    def from_data(cls, data: Dict[str, Any] | None) -> "VarSources":
        data = data or {}
        return cls(
            cvars=dict(data.get("cvars") or {}),
            uvars=dict(data.get("uvars") or {}),
            svars=dict(data.get("svars") or {}),
            gvars=dict(data.get("gvars") or {}),
        )

    def merge(self, other: "VarSources") -> "VarSources":
        def _merge(lhs: Dict[str, Any], rhs: Dict[str, Any]) -> Dict[str, Any]:
            merged = dict(lhs)
            merged.update(rhs)
            return merged

        return VarSources(
            cvars=_merge(self.cvars, other.cvars),
            uvars=_merge(self.uvars, other.uvars),
            svars=_merge(self.svars, other.svars),
            gvars=_merge(self.gvars, other.gvars),
        )

    def to_initial_names(self) -> Dict[str, Any]:
        names: Dict[str, Any] = {}
        # Bind uvars first so cvars take precedence, matching Avrae's local > cvar > uvar lookup.
        names.update(self.uvars)
        names.update(self.cvars)
        return names


@dataclass
class ContextProfile:
    name: str
    ctx: Dict[str, Any] = field(default_factory=dict)
    combat: Dict[str, Any] = field(default_factory=dict)
    character: Dict[str, Any] = field(default_factory=dict)
    vars: VarSources = field(default_factory=VarSources)
    description: str = ""


@dataclass
class AvraeLSConfig:
    workspace_root: Path
    enable_gvar_fetch: bool = False
    service: AvraeServiceConfig = field(default_factory=AvraeServiceConfig)
    var_files: Tuple[Path, ...] = field(default_factory=tuple)
    default_profile: str = "default"
    profiles: Dict[str, ContextProfile] = field(default_factory=dict)
    diagnostics: DiagnosticSettings = field(default_factory=DiagnosticSettings)

    @classmethod
    def default(cls, workspace_root: Path) -> "AvraeLSConfig":
        abilities = {
            "strength": 16,
            "dexterity": 14,
            "constitution": 15,
            "intelligence": 10,
            "wisdom": 12,
            "charisma": 13,
            "prof_bonus": 3,
        }

        def _mod(score: int) -> int:
            return math.floor((score - 10) / 2)

        skill_profs = {"athletics", "perception", "stealth", "survival"}
        skills = {}
        for name, ability in {
            "acrobatics": "dexterity",
            "animalHandling": "wisdom",
            "arcana": "intelligence",
            "athletics": "strength",
            "deception": "charisma",
            "history": "intelligence",
            "initiative": "dexterity",
            "insight": "wisdom",
            "intimidation": "charisma",
            "investigation": "intelligence",
            "medicine": "wisdom",
            "nature": "intelligence",
            "perception": "wisdom",
            "performance": "charisma",
            "persuasion": "charisma",
            "religion": "intelligence",
            "sleightOfHand": "dexterity",
            "stealth": "dexterity",
            "survival": "wisdom",
            "strength": "strength",
            "dexterity": "dexterity",
            "constitution": "constitution",
            "intelligence": "intelligence",
            "wisdom": "wisdom",
            "charisma": "charisma",
        }.items():
            base = _mod(abilities[ability])
            prof = 1 if name in skill_profs else 0
            skills[name] = {"value": base + abilities["prof_bonus"] * prof, "prof": prof, "bonus": 0, "adv": None}

        saves = {
            "str": _mod(abilities["strength"]) + abilities["prof_bonus"],
            "dex": _mod(abilities["dexterity"]),
            "con": _mod(abilities["constitution"]) + abilities["prof_bonus"],
            "int": _mod(abilities["intelligence"]),
            "wis": _mod(abilities["wisdom"]),
            "cha": _mod(abilities["charisma"]),
        }

        attacks = [
            {
                "name": "Longsword",
                "verb": "swings",
                "proper": False,
                "activation_type": 1,
                "raw": {"name": "Longsword", "bonus": "+7", "damage": "1d8+4 slashing"},
            },
            {
                "name": "Shortbow",
                "verb": "looses",
                "proper": False,
                "activation_type": 1,
                "raw": {"name": "Shortbow", "bonus": "+6", "damage": "1d6+3 piercing"},
            },
        ]

        spellbook = {
            "dc": 14,
            "sab": 6,
            "caster_level": 5,
            "spell_mod": 3,
            "pact_slot_level": None,
            "num_pact_slots": None,
            "max_pact_slots": None,
            "slots": {1: 4, 2: 2},
            "max_slots": {1: 4, 2: 2},
            "spells": [
                {"name": "Cure Wounds", "dc": None, "sab": None, "mod": None, "prepared": True},
                {"name": "Hunter's Mark", "dc": None, "sab": None, "mod": None, "prepared": True},
                {"name": "Fire Bolt", "dc": None, "sab": None, "mod": None, "prepared": True},
            ],
        }

        consumables = {
            "Hit Dice": {
                "name": "Hit Dice",
                "value": 5,
                "max": 5,
                "min": 0,
                "reset_on": "long",
                "display_type": None,
                "reset_to": None,
                "reset_by": None,
                "title": "d10 hit dice",
                "desc": "Hit dice pool",
            },
            "Bardic Inspiration": {
                "name": "Bardic Inspiration",
                "value": 3,
                "max": 3,
                "min": 0,
                "reset_on": "long",
                "display_type": "bubble",
                "reset_to": "max",
                "reset_by": None,
                "title": None,
                "desc": None,
            },
        }

        character = {
            "name": "Aelar Wyn",
            "race": "Half-Elf",
            "background": "Outlander",
            "description": "Scout of the Emerald Enclave.",
            "image": "https://example.invalid/aelar.png",
            "owner": 1010101010,
            "upstream": "char_aelar_wyn",
            "sheet_type": "beyond",
            "creature_type": "humanoid",
            "stats": abilities,
            "levels": {"Fighter": 3, "Rogue": 2},
            "attacks": attacks,
            "actions": [
                {
                    "name": "Second Wind",
                    "activation_type": 3,
                    "activation_type_name": "BONUS_ACTION",
                    "description": "+1d10+5 hp",
                    "snippet": "+1d10+5 hp",
                },
                {
                    "name": "Action Surge",
                    "activation_type": 1,
                    "activation_type_name": "ACTION",
                    "description": "Take one additional action.",
                    "snippet": "Take one additional action.",
                },
            ],
            "skills": skills,
            "saves": saves,
            "resistances": {"resist": [{"dtype": "fire", "unless": [], "only": []}], "vuln": [], "immune": [], "neutral": []},
            "spellbook": spellbook,
            "consumables": consumables,
            "cvars": {"favorite_enemy": "goblinoids", "fighting_style": "defense"},
            "coinpurse": {"pp": 1, "gp": 47, "ep": 0, "sp": 12, "cp": 34},
            "death_saves": {"successes": 0, "fails": 0},
            "max_hp": 44,
            "hp": 38,
            "temp_hp": 3,
            "ac": 17,
            "passive_perception": 15,
            "speed": 30,
            "class_levels": {"Fighter": 3, "Rogue": 2},
            "csettings": {"compact_coins": False},
        }

        me_combatant = {
            "name": character["name"],
            "id": "cmb_aelar",
            "controller": character["owner"],
            "group": None,
            "race": character["race"],
            "monster_name": None,
            "is_hidden": False,
            "init": 18,
            "initmod": 4,
            "type": "combatant",
            "note": "On watch",
            "effects": [
                {
                    "name": "Hunter's Mark",
                    "duration": 600,
                    "remaining": 540,
                    "desc": "Mark one target; deal +1d6 damage to it.",
                    "concentration": True,
                    "combatant_name": character["name"],
                }
            ],
            "stats": character["stats"],
            "levels": character["levels"],
            "skills": character["skills"],
            "saves": character["saves"],
            "resistances": character["resistances"],
            "spellbook": character["spellbook"],
            "attacks": character["attacks"],
            "max_hp": character["max_hp"],
            "hp": character["hp"],
            "temp_hp": character["temp_hp"],
            "ac": character["ac"],
            "creature_type": character["creature_type"],
        }

        goblin = {
            "name": "Goblin Cutter",
            "id": "cmb_gob1",
            "controller": None,
            "group": "Goblins",
            "race": None,
            "monster_name": "Goblin",
            "is_hidden": False,
            "init": 12,
            "initmod": 2,
            "type": "combatant",
            "note": "",
            "effects": [],
            "stats": {"strength": 8, "dexterity": 14, "constitution": 10, "intelligence": 8, "wisdom": 10, "charisma": 8, "prof_bonus": 2},
            "levels": {},
            "skills": {"stealth": {"value": 6, "prof": 1, "bonus": 0, "adv": None}},
            "saves": {"dex": 4},
            "resistances": {"resist": [], "vuln": [], "immune": [], "neutral": []},
            "spellbook": {"spells": []},
            "attacks": [{"name": "Scimitar", "verb": "slashes", "proper": False, "activation_type": 1, "raw": {"name": "Scimitar", "bonus": "+4", "damage": "1d6+2 slashing"}}],
            "max_hp": 11,
            "hp": 11,
            "temp_hp": 0,
            "ac": 15,
            "creature_type": "humanoid",
        }

        group = {"name": "Goblins", "id": "grp_goblins", "init": 12, "type": "group", "combatants": [goblin]}

        combat = {
            "name": "Goblin Ambush",
            "round_num": 2,
            "turn_num": 15,
            "combatants": [me_combatant, goblin],
            "groups": [group],
            "me": me_combatant,
            "current": group,
            "metadata": {"scene": "Forest road", "weather": "light rain"},
        }

        ctx = {
            "guild": {"id": 123456789012345678, "name": "Fabled Realms"},
            "channel": {
                "id": 234567890123456789,
                "name": "tavern-rp",
                "topic": "Adventuring party chat",
                "category": {"id": 9876543210, "name": "Adventures"},
                "parent": None,
            },
            "author": {
                "id": 345678901234567890,
                "name": "AelarW",
                "discriminator": "0420",
                "display_name": "Aelar Wyn",
                "roles": [{"id": 4567, "name": "DM"}, {"id": 4568, "name": "Player"}],
            },
            "prefix": "!",
            "alias": "mockalias",
            "message_id": 456789012345678901,
        }

        profile = ContextProfile(
            name="default",
            ctx=ctx,
            combat=combat,
            character=character,
            description="Built-in Avrae LS mock profile with realistic sample data.",
        )
        return cls(
            workspace_root=workspace_root,
            profiles={"default": profile},
        )


def load_config(workspace_root: Path) -> Tuple[AvraeLSConfig, Iterable[str]]:
    """Load `.avraels.json` from the workspace root, returning config and warnings."""
    path = workspace_root / CONFIG_FILENAME
    if not path.exists():
        return AvraeLSConfig.default(workspace_root), []

    try:
        raw = json.loads(path.read_text())
    except json.JSONDecodeError as exc:
        warning = f"Failed to parse {CONFIG_FILENAME}: {exc}"
        log.warning(warning)
        return AvraeLSConfig.default(workspace_root), [warning]

    warnings: list[str] = []
    enable_gvar_fetch = bool(raw.get("enableGvarFetch", False))

    service_cfg = raw.get("avraeService") or {}
    service = AvraeServiceConfig(
        base_url=str(service_cfg.get("baseUrl") or AvraeServiceConfig.base_url),
        token=service_cfg.get("token"),
    )

    diag_cfg = raw.get("diagnostics") or {}
    diagnostics = DiagnosticSettings(
        semantic_level=str(diag_cfg.get("semanticLevel") or "warning").lower(),
        runtime_level=str(diag_cfg.get("runtimeLevel") or "error").lower(),
    )

    var_files = tuple(
        _resolve_var_file(workspace_root, file_path)
        for file_path in raw.get("varFiles", [])
        if isinstance(file_path, str)
    )

    profiles: Dict[str, ContextProfile] = {}
    raw_profiles = raw.get("profiles") or {}
    for name, data in raw_profiles.items():
        profiles[name] = ContextProfile(
            name=name,
            ctx=dict(data.get("ctx") or {}),
            combat=dict(data.get("combat") or {}),
            character=dict(data.get("character") or {}),
            vars=VarSources.from_data(data.get("vars")),
            description=str(data.get("description") or ""),
        )

    default_profile = str(raw.get("defaultProfile") or "default")
    if default_profile not in profiles and profiles:
        warnings.append(
            f"defaultProfile '{default_profile}' not found; falling back to first profile in file."
        )
        default_profile = next(iter(profiles))

    if not profiles:
        base = AvraeLSConfig.default(workspace_root)
        profiles = base.profiles
        default_profile = base.default_profile

    cfg = AvraeLSConfig(
        workspace_root=workspace_root,
        enable_gvar_fetch=enable_gvar_fetch,
        service=service,
        var_files=var_files,
        default_profile=default_profile,
        profiles=profiles,
        diagnostics=diagnostics,
    )
    return cfg, warnings


def _resolve_var_file(root: Path, file_path: str) -> Path:
    candidate = Path(file_path)
    if not candidate.is_absolute():
        candidate = root / candidate
    return candidate
