from __future__ import annotations

import json
import os
from dataclasses import dataclass
from datetime import datetime
from typing import Union

from dotenv import load_dotenv

@dataclass
class Settings:
    bot_token: str
    admin_id: int
    db_path: str
    event_json: str
    welcome_json: str
    archive_json: str


def load_settings() -> Settings:
    load_dotenv()
    event_json = os.environ.get("EVENT_JSON") or os.environ.get("CONFERENCES_JSON") or "event.json"
    return Settings(
        bot_token=os.environ["BOT_TOKEN"],
        admin_id=int(os.environ["ADMIN_ID"]),
        db_path=os.environ.get("DB_PATH", "conference_bot.db"),
        event_json=event_json,
        welcome_json=os.environ.get("WELCOME_JSON", "welcome.json"),
        archive_json=os.environ.get("ARCHIVE_JSON", "archive.json"),
    )


@dataclass
class Speaker:
    name: str
    channel: str


@dataclass
class ConferenceEvent:
    event_id: str
    type: str
    title: str
    speakers: list[Speaker]


@dataclass
class WebinarEvent:
    event_id: str
    type: str
    title: str
    description: str
    datetime: datetime
    duration_minutes: int


@dataclass
class IdleEvent:
    event_id: str
    type: str
    title: str


Event = Union[ConferenceEvent, WebinarEvent, IdleEvent]


def load_event(path: str) -> Event:
    with open(path, "rb") as f:
        raw = f.read()
    return validate_event_json(raw)


@dataclass
class WelcomeConfig:
    text: str
    photo_file_id: str | None = None


def load_welcome(path: str) -> WelcomeConfig | None:
    """Load welcome config from file. Returns None if file doesn't exist."""
    try:
        with open(path, encoding="utf-8") as f:
            data = json.load(f)
        return WelcomeConfig(
            text=data.get("text", ""),
            photo_file_id=data.get("photo_file_id") or None,
        )
    except FileNotFoundError:
        return None


def validate_event_json(raw: bytes) -> Event:
    """Parse and validate event JSON. Supports new schema (event_id+type) and
    legacy conference schema (conference_id, no type). Raises ValueError on
    invalid input."""
    try:
        data = json.loads(raw)
    except json.JSONDecodeError as e:
        raise ValueError(f"Невалидный JSON: {e}") from e
    if not isinstance(data, dict):
        raise ValueError("JSON должен быть объектом, а не списком или строкой")

    # Legacy: only conference_id, no type → infer
    if "conference_id" in data and "type" not in data and "event_id" not in data:
        data = {**data, "event_id": data["conference_id"], "type": "conference"}

    event_type = data.get("type")
    if event_type == "conference":
        return _parse_conference(data)
    if event_type == "webinar":
        return _parse_webinar(data)
    if event_type == "idle":
        return _parse_idle(data)
    raise ValueError(f"Неизвестный type: {event_type!r}. Допустимы 'conference', 'webinar', 'idle'.")


def _parse_conference(data: dict) -> ConferenceEvent:
    required = {"event_id", "type", "title", "speakers"}
    missing = required - data.keys()
    if missing:
        raise ValueError(f"Отсутствуют поля: {missing}")
    if not isinstance(data["event_id"], str):
        raise ValueError("event_id должен быть строкой")
    if not isinstance(data["title"], str):
        raise ValueError("title должен быть строкой")
    if not isinstance(data["speakers"], list) or len(data["speakers"]) == 0:
        raise ValueError("speakers должен быть непустым списком")
    for s in data["speakers"]:
        if "name" not in s or "channel" not in s:
            raise ValueError(f"Спикер без name или channel: {s}")
        if not s["channel"].startswith("@"):
            raise ValueError(f"channel должен начинаться с @: {s['channel']}")
    return ConferenceEvent(
        event_id=data["event_id"],
        type="conference",
        title=data["title"],
        speakers=[Speaker(name=s["name"], channel=s["channel"]) for s in data["speakers"]],
    )


def _parse_webinar(data: dict) -> WebinarEvent:
    required = {"event_id", "type", "title", "description", "datetime", "duration_minutes"}
    missing = required - data.keys()
    if missing:
        raise ValueError(f"Отсутствуют поля: {missing}")
    if not isinstance(data["event_id"], str):
        raise ValueError("event_id должен быть строкой")
    if not isinstance(data["title"], str):
        raise ValueError("title должен быть строкой")
    if not isinstance(data["description"], str):
        raise ValueError("description должен быть строкой")
    try:
        dt = datetime.fromisoformat(data["datetime"])
    except (TypeError, ValueError) as e:
        raise ValueError(f"datetime должен быть строкой ISO 8601: {e}") from e
    if dt.tzinfo is None:
        raise ValueError("datetime должен содержать timezone (например, '+03:00')")
    if not isinstance(data["duration_minutes"], int) or data["duration_minutes"] <= 0:
        raise ValueError("duration_minutes должен быть положительным целым")
    return WebinarEvent(
        event_id=data["event_id"],
        type="webinar",
        title=data["title"],
        description=data["description"],
        datetime=dt,
        duration_minutes=data["duration_minutes"],
    )


def _parse_idle(data: dict) -> IdleEvent:
    required = {"event_id", "type", "title"}
    missing = required - data.keys()
    if missing:
        raise ValueError(f"Отсутствуют поля: {missing}")
    if not isinstance(data["event_id"], str):
        raise ValueError("event_id должен быть строкой")
    if not isinstance(data["title"], str):
        raise ValueError("title должен быть строкой")
    return IdleEvent(
        event_id=data["event_id"],
        type="idle",
        title=data["title"],
    )
