from __future__ import annotations

import json
import os
from dataclasses import dataclass
from pathlib import Path
from typing import Any

from src.errors import UserError

CONFIG_FILE_USED_ENV_VAR = "EVENTFOTO_CONFIG_FILE_USED"


@dataclass(frozen=True)
class LoadedConfigFile:
    path: Path
    values: dict[str, str]


def resolve_config_path(cli_value: str | None) -> Path | None:
    raw = (cli_value or "").strip()
    if not raw:
        return None
    return Path(raw)


def apply_config_file(*, cli_value: str | None, override_env: bool) -> LoadedConfigFile | None:
    path = resolve_config_path(cli_value)
    if path is None:
        return None

    loaded = load_config_file(path)
    apply_to_environ(loaded.values, override_env=override_env)
    os.environ[CONFIG_FILE_USED_ENV_VAR] = str(loaded.path)
    return loaded


def load_config_file(path: Path) -> LoadedConfigFile:
    if not path.exists():
        raise UserError(f"Config file does not exist: {path}")
    if not path.is_file():
        raise UserError(f"Config path is not a file: {path}")

    try:
        text = path.read_text(encoding="utf-8")
    except OSError as exc:
        raise UserError(f"Failed to read config file: {path} ({exc})") from exc
    if path.suffix.lower() == ".json":
        values = _parse_json(text)
    else:
        values = _parse_env(text)
    return LoadedConfigFile(path=path, values=values)


def apply_to_environ(values: dict[str, str], *, override_env: bool) -> None:
    for k, v in values.items():
        key = str(k).strip()
        if not key:
            continue
        if not override_env and os.environ.get(key, "").strip():
            continue
        os.environ[key] = str(v)


def _parse_json(text: str) -> dict[str, str]:
    try:
        raw = json.loads(text or "")
    except json.JSONDecodeError as exc:
        raise UserError(f"Invalid JSON config file: {exc.msg}") from exc
    if not isinstance(raw, dict):
        raise UserError("JSON config file must contain an object at the top level.")

    out: dict[str, str] = {}
    for k, v in raw.items():
        out[str(k)] = _json_value_to_env(v)
    return out


def _json_value_to_env(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, (dict, list)):
        return json.dumps(value, ensure_ascii=True, sort_keys=True)
    return str(value)


def _parse_env(text: str) -> dict[str, str]:
    out: dict[str, str] = {}
    for i, line in enumerate((text or "").splitlines(), start=1):
        stripped = line.strip()
        if not stripped or stripped.startswith("#"):
            continue
        if "=" not in stripped:
            raise UserError(f"Invalid config line (expected KEY=VALUE) at line {i}.")
        key, value = stripped.split("=", 1)
        k = key.strip()
        if not k:
            raise UserError(f"Invalid config line (empty key) at line {i}.")
        out[k] = _strip_quotes(value.strip())
    return out


def _strip_quotes(value: str) -> str:
    if len(value) >= 2 and (
        (value.startswith('"') and value.endswith('"'))
        or (value.startswith("'") and value.endswith("'"))
    ):
        return value[1:-1]
    return value
