from __future__ import annotations

from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from app.db import reset_db_caches
from app.config import Settings, get_settings
from app.models.config import AppConfigEntry


@dataclass(frozen=True, slots=True)
class ConfigField:
    section: str
    env_name: str
    setting_attr: str
    label: str
    secret: bool = False
    input_type: str = "text"


CONFIG_FIELDS: tuple[ConfigField, ...] = (
    ConfigField("System", "APP_NAME", "app_name", "App Name"),
    ConfigField("System", "APP_ENV", "app_env", "App Env"),
    ConfigField("System", "APP_DEBUG", "app_debug", "App Debug", input_type="checkbox"),
    ConfigField("System", "APP_HOSTNAME", "app_hostname", "App Hostname"),
    ConfigField("SMTP", "SMTP_ENABLED", "smtp_enabled", "SMTP Enabled", input_type="checkbox"),
    ConfigField("SMTP", "SMTP_HOST", "smtp_host", "SMTP Host"),
    ConfigField("SMTP", "SMTP_PORT", "smtp_port", "SMTP Port"),
    ConfigField("SMTP", "SMTP_USERNAME", "smtp_username", "SMTP Username"),
    ConfigField("SMTP", "SMTP_PASSWORD", "smtp_password", "SMTP Password", secret=True),
    ConfigField("SMTP", "SMTP_FROM_NAME", "smtp_from_name", "SMTP From Name"),
    ConfigField("SMTP", "SMTP_FROM_ADDRESS", "smtp_from_address", "SMTP From Address"),
    ConfigField("SMTP", "SMTP_TO_ADDRESS", "smtp_to_address", "SMTP To Address"),
    ConfigField("SMTP", "SMTP_USE_STARTTLS", "smtp_use_starttls", "SMTP Use STARTTLS", input_type="checkbox"),
    ConfigField(
        "Authentication",
        "AUTH_SESSION_COOKIE_NAME",
        "auth_session_cookie_name",
        "Session Cookie Name",
    ),
    ConfigField("Authentication", "AUTH_SESSION_TTL_HOURS", "auth_session_ttl_hours", "Session TTL Hours"),
    ConfigField(
        "Authentication",
        "AUTH_COOKIE_SECURE_OVERRIDE",
        "auth_cookie_secure_override",
        "Cookie Secure Override",
    ),
    ConfigField(
        "Authentication",
        "AUTH_LOGIN_THROTTLE_ENABLED",
        "auth_login_throttle_enabled",
        "Login Throttle Enabled",
        input_type="checkbox",
    ),
    ConfigField("Poo", "POO_WEBHOOK_ID", "poo_webhook_id", "Poo Webhook ID", secret=True),
    ConfigField(
        "Poo",
        "POO_SENSOR_ENTITY_NAME",
        "poo_sensor_entity_name",
        "Poo Sensor Entity Name",
    ),
    ConfigField(
        "Poo",
        "POO_SENSOR_FRIENDLY_NAME",
        "poo_sensor_friendly_name",
        "Poo Sensor Friendly Name",
    ),
    ConfigField("TickTick", "TICKTICK_CLIENT_ID", "ticktick_client_id", "TickTick Client ID"),
    ConfigField(
        "TickTick",
        "TICKTICK_CLIENT_SECRET",
        "ticktick_client_secret",
        "TickTick Client Secret",
        secret=True,
    ),
    ConfigField("TickTick", "TICKTICK_TOKEN", "ticktick_token", "TickTick Token", secret=True),
    ConfigField(
        "Home Assistant",
        "HOME_ASSISTANT_BASE_URL",
        "home_assistant_base_url",
        "Home Assistant Base URL",
    ),
    ConfigField(
        "Home Assistant",
        "HOME_ASSISTANT_AUTH_TOKEN",
        "home_assistant_auth_token",
        "Home Assistant Auth Token",
        secret=True,
    ),
    ConfigField(
        "Home Assistant",
        "HOME_ASSISTANT_TIMEOUT_SECONDS",
        "home_assistant_timeout_seconds",
        "Home Assistant Timeout Seconds",
    ),
    ConfigField(
        "Home Assistant",
        "HOME_ASSISTANT_ACTION_TASK_PROJECT_ID",
        "home_assistant_action_task_project_id",
        "Home Assistant Action Task Project ID",
    ),
    ConfigField("MQTT", "MQTT_ENABLED", "mqtt_enabled", "MQTT Enabled", input_type="checkbox"),
    ConfigField("MQTT", "MQTT_BROKER_HOST", "mqtt_broker_host", "MQTT Broker Host"),
    ConfigField("MQTT", "MQTT_BROKER_PORT", "mqtt_broker_port", "MQTT Broker Port", input_type="number"),
    ConfigField("MQTT", "MQTT_USERNAME", "mqtt_username", "MQTT Username"),
    ConfigField("MQTT", "MQTT_PASSWORD", "mqtt_password", "MQTT Password", secret=True),
    ConfigField("MQTT", "MQTT_TLS_ENABLED", "mqtt_tls_enabled", "MQTT TLS Enabled", input_type="checkbox"),
    ConfigField(
        "Home Assistant Discovery",
        "HA_DISCOVERY_ENABLED",
        "ha_discovery_enabled",
        "HA Discovery Enabled",
        input_type="checkbox",
    ),
    ConfigField(
        "Home Assistant Discovery",
        "HA_DISCOVERY_PREFIX",
        "ha_discovery_prefix",
        "HA Discovery Prefix",
    ),
    ConfigField(
        "Home Assistant Discovery",
        "HA_STATE_TOPIC_PREFIX",
        "ha_state_topic_prefix",
        "HA State Topic Prefix",
    ),
    ConfigField("Modbus", "MODBUS_POLLING_ENABLED", "modbus_polling_enabled", "Modbus Polling Enabled", input_type="checkbox"),
    ConfigField(
        "DSMR",
        "DSMR_INGEST_ENABLED",
        "dsmr_ingest_enabled",
        "DSMR Ingest Enabled",
        input_type="checkbox",
    ),
    ConfigField("DSMR", "DSMR_MQTT_TOPIC", "dsmr_mqtt_topic", "DSMR MQTT Topic"),
    ConfigField(
        "DSMR",
        "DSMR_SAMPLE_INTERVAL_S",
        "dsmr_sample_interval_s",
        "DSMR Sample Interval (s)",
        input_type="number",
    ),
    ConfigField(
        "DSMR",
        "DSMR_TARIFF_TOPIC",
        "dsmr_tariff_topic",
        "DSMR Tariff Topic",
    ),
    ConfigField(
        "Tibber",
        "TIBBER_API_TOKEN",
        "tibber_api_token",
        "Tibber API Token",
        secret=True,
    ),
    ConfigField("Tibber", "TIBBER_HOME_ID", "tibber_home_id", "Tibber Home ID"),
)


class ConfigSaveError(ValueError):
    """Raised when the submitted config payload is invalid."""


def seed_missing_config_from_bootstrap(session: Session, bootstrap_settings: Settings) -> None:
    current_values = _read_config_values(session)
    missing_values: dict[str, str] = {}

    for field in CONFIG_FIELDS:
        if field.env_name in current_values:
            continue
        missing_values[field.env_name] = _stringify(getattr(bootstrap_settings, field.setting_attr))

    if not missing_values:
        return

    _persist_config_values(session, {**current_values, **missing_values})


def sync_app_hostname_from_bootstrap(session: Session, bootstrap_settings: Settings) -> None:
    current_values = _read_config_values(session)
    bootstrap_hostname = _stringify(bootstrap_settings.app_hostname)
    if current_values.get("APP_HOSTNAME") == bootstrap_hostname:
        return

    current_values["APP_HOSTNAME"] = bootstrap_hostname
    _persist_config_values(session, current_values)
    get_settings.cache_clear()
    reset_db_caches()


def build_runtime_settings(session: Session, bootstrap_settings: Settings) -> Settings:
    overrides = _read_config_values(session)
    if not overrides:
        return bootstrap_settings

    payload = _settings_payload(bootstrap_settings)
    for field in CONFIG_FIELDS:
        if field.env_name in overrides:
            payload[field.setting_attr] = overrides[field.env_name]

    return Settings(_env_file=None, **payload)


def build_config_sections(session: Session, bootstrap_settings: Settings) -> list[dict[str, Any]]:
    runtime_settings = build_runtime_settings(session, bootstrap_settings)
    persisted_values = _read_config_values(session)
    sections: list[dict[str, Any]] = []
    current_section: dict[str, Any] | None = None

    for field in CONFIG_FIELDS:
        if current_section is None or current_section["name"] != field.section:
            current_section = {"name": field.section, "fields": []}
            sections.append(current_section)

        current_section["fields"].append(
            {
                "env_name": field.env_name,
                "label": field.label,
                "value": "" if field.secret else _stringify(getattr(runtime_settings, field.setting_attr)),
                "secret": field.secret,
                "input_type": "password" if field.secret else field.input_type,
                "configured": field.env_name in persisted_values
                or bool(_stringify(getattr(bootstrap_settings, field.setting_attr))),
            }
        )

    return sections


def save_config_updates(session: Session, form_data: dict[str, str], bootstrap_settings: Settings) -> None:
    current_values = _read_config_values(session)
    merged_values = dict(current_values)

    for field in CONFIG_FIELDS:
        submitted_value = form_data.get(field.env_name, "")
        if field.secret:
            if submitted_value:
                merged_values[field.env_name] = submitted_value
        else:
            merged_values[field.env_name] = submitted_value

    _validate_config_values(merged_values, bootstrap_settings)
    _persist_config_values(session, merged_values)
    get_settings.cache_clear()
    reset_db_caches()


def save_config_value(
    session: Session,
    *,
    env_name: str,
    value: str,
    bootstrap_settings: Settings,
) -> None:
    current_values = _read_config_values(session)
    current_values[env_name] = value
    _validate_config_values(current_values, bootstrap_settings)
    _persist_config_values(session, current_values)
    get_settings.cache_clear()
    reset_db_caches()


def is_ticktick_oauth_ready(settings: Settings) -> bool:
    return bool(
        settings.app_hostname
        and settings.ticktick_client_id
        and settings.ticktick_client_secret
    )


def _read_config_values(session: Session) -> dict[str, str]:
    rows = session.execute(select(AppConfigEntry).order_by(AppConfigEntry.key)).scalars().all()
    return {row.key: row.value for row in rows}


def _validate_config_values(config_values: dict[str, str], bootstrap_settings: Settings) -> None:
    payload = _settings_payload(bootstrap_settings)
    for field in CONFIG_FIELDS:
        if field.env_name in config_values:
            payload[field.setting_attr] = config_values[field.env_name]

    try:
        Settings(_env_file=None, **payload)
    except Exception as exc:
        raise ConfigSaveError("invalid config submission") from exc


def _persist_config_values(session: Session, config_values: dict[str, str]) -> None:
    existing_entries = {
        row.key: row
        for row in session.execute(select(AppConfigEntry)).scalars().all()
    }
    now = datetime.now(UTC)

    for env_name, value in config_values.items():
        entry = existing_entries.get(env_name)
        if entry is None:
            session.add(AppConfigEntry(key=env_name, value=value, updated_at=now))
        else:
            entry.value = value
            entry.updated_at = now

    session.commit()


def _stringify(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, bool):
        return str(value).lower()
    return str(value)


def _settings_payload(settings: Settings) -> dict[str, Any]:
    return {
        "app_name": settings.app_name,
        "app_env": settings.app_env,
        "app_debug": settings.app_debug,
        "app_hostname": settings.app_hostname,
        "app_database_url": settings.app_database_url,
        "ticktick_client_id": settings.ticktick_client_id,
        "ticktick_client_secret": settings.ticktick_client_secret,
        "ticktick_token": settings.ticktick_token,
        "home_assistant_base_url": settings.home_assistant_base_url,
        "home_assistant_auth_token": settings.home_assistant_auth_token,
        "home_assistant_timeout_seconds": settings.home_assistant_timeout_seconds,
        "home_assistant_action_task_project_id": settings.home_assistant_action_task_project_id,
        "smtp_enabled": settings.smtp_enabled,
        "smtp_host": settings.smtp_host,
        "smtp_port": settings.smtp_port,
        "smtp_username": settings.smtp_username,
        "smtp_password": settings.smtp_password,
        "smtp_from_name": settings.smtp_from_name,
        "smtp_from_address": settings.smtp_from_address,
        "smtp_to_address": settings.smtp_to_address,
        "smtp_use_starttls": settings.smtp_use_starttls,
        "poo_webhook_id": settings.poo_webhook_id,
        "poo_sensor_entity_name": settings.poo_sensor_entity_name,
        "poo_sensor_friendly_name": settings.poo_sensor_friendly_name,
        "auth_bootstrap_username": settings.auth_bootstrap_username,
        "auth_bootstrap_password": settings.auth_bootstrap_password,
        "auth_session_cookie_name": settings.auth_session_cookie_name,
        "auth_session_ttl_hours": settings.auth_session_ttl_hours,
        "auth_cookie_secure_override": settings.auth_cookie_secure_override,
        "auth_login_throttle_enabled": settings.auth_login_throttle_enabled,
        "auth_trust_forwarded_for": settings.auth_trust_forwarded_for,
        "modbus_polling_enabled": settings.modbus_polling_enabled,
        "mqtt_enabled": settings.mqtt_enabled,
        "mqtt_broker_host": settings.mqtt_broker_host,
        "mqtt_broker_port": settings.mqtt_broker_port,
        "mqtt_username": settings.mqtt_username,
        "mqtt_password": settings.mqtt_password,
        "mqtt_tls_enabled": settings.mqtt_tls_enabled,
        "ha_discovery_enabled": settings.ha_discovery_enabled,
        "ha_discovery_prefix": settings.ha_discovery_prefix,
        "ha_state_topic_prefix": settings.ha_state_topic_prefix,
        "dsmr_ingest_enabled": settings.dsmr_ingest_enabled,
        "dsmr_mqtt_topic": settings.dsmr_mqtt_topic,
        "dsmr_sample_interval_s": settings.dsmr_sample_interval_s,
        "dsmr_tariff_topic": settings.dsmr_tariff_topic,
        "tibber_api_token": settings.tibber_api_token,
        "tibber_home_id": settings.tibber_home_id,
    }
