from __future__ import annotations

import os
from dataclasses import dataclass
from pathlib import Path

from dotenv import load_dotenv

BASE_DIR = Path(__file__).resolve().parent
# Passenger may import the application with a different working directory.
# Always resolve .env and relative database paths from the project directory.
load_dotenv(BASE_DIR / ".env")


@dataclass(frozen=True)
class Settings:
    bot_token: str
    admin_ids: tuple[int, ...]
    db_engine: str
    db_path: str
    db_host: str | None = None
    db_port: int = 3306
    db_name: str | None = None
    db_user: str | None = None
    db_password: str | None = None
    db_pool_size: int = 8
    db_connect_timeout: int = 10
    report_chat_id: int | str | None = None
    output_chat_id: int | str | None = None
    news_chat_id: int | str | None = None
    webhook_url: str | None = None
    webhook_secret: str | None = None

    @classmethod
    def from_env(cls, require_bot: bool = True) -> "Settings":
        token = os.getenv("BOT_TOKEN", "").strip()
        if require_bot and not token:
            raise RuntimeError("BOT_TOKEN is missing. Copy .env.example to .env and set it.")
        admins: list[int] = []
        for raw in os.getenv("ADMIN_IDS", "").split(","):
            raw = raw.strip()
            if raw:
                try:
                    admins.append(int(raw))
                except ValueError as exc:
                    raise RuntimeError(f"Invalid ADMIN_IDS value: {raw}") from exc
        if require_bot and not admins:
            raise RuntimeError("ADMIN_IDS must contain at least one Telegram user id.")
        db_engine = os.getenv("DB_ENGINE", "mysql").strip().lower()
        if db_engine not in {"mysql", "mariadb", "sqlite"}:
            raise RuntimeError("DB_ENGINE must be mysql, mariadb or sqlite.")
        db_path = os.getenv("DB_PATH", "./data/bot.sqlite3")
        db_path = str(Path(db_path).expanduser() if Path(db_path).is_absolute() else BASE_DIR / db_path)
        if db_engine == "sqlite":
            Path(db_path).parent.mkdir(parents=True, exist_ok=True)

        def integer_env(name: str, default: int) -> int:
            raw = os.getenv(name, str(default)).strip()
            try:
                value = int(raw)
            except ValueError as exc:
                raise RuntimeError(f"Invalid {name} value: {raw}") from exc
            return value

        db_host = os.getenv("DB_HOST", "localhost").strip()
        db_port = integer_env("DB_PORT", 3306)
        db_name = os.getenv("DB_NAME", "").strip() or None
        db_user = os.getenv("DB_USER", "").strip() or None
        db_password = os.getenv("DB_PASSWORD", "")
        if db_engine in {"mysql", "mariadb"} and (not db_name or not db_user):
            raise RuntimeError("DB_NAME and DB_USER are required when DB_ENGINE=mysql.")
        def chat_ref(name: str) -> int | str | None:
            raw = os.getenv(name, "").strip()
            if not raw:
                return None
            return int(raw) if raw.lstrip("-").isdigit() else raw
        webhook_url = os.getenv("WEBHOOK_URL", "").strip() or None
        webhook_secret = os.getenv("WEBHOOK_SECRET", "").strip() or None
        return cls(
            token,
            tuple(admins),
            db_engine,
            db_path,
            db_host,
            db_port,
            db_name,
            db_user,
            db_password,
            max(2, integer_env("DB_POOL_SIZE", 8)),
            max(3, integer_env("DB_CONNECT_TIMEOUT", 10)),
            chat_ref("REPORT_CHAT_ID"),
            chat_ref("OUTPUT_CHAT_ID"),
            chat_ref("NEWS_CHAT_ID"),
            webhook_url,
            webhook_secret,
        )
