"""Settings router for persistent machine definitions, app credentials, and data reset."""

from __future__ import annotations

import logging
from io import StringIO
from typing import Any

import paramiko
from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel, Field

from media_library_viewer_api.clients.ssh import RemoteSSHClient
from media_library_viewer_api.config import get_settings
from media_library_viewer_api.dependencies import get_settings_store
from media_library_viewer_api.services.db_maintenance import remove_sqlite_database
from media_library_viewer_api.services.known_hosts import has_known_host
from media_library_viewer_api.services.media_index import MediaIndex
from media_library_viewer_api.services.settings_store import SettingsStore

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/settings", tags=["settings"])


class MonitoringMachineInput(BaseModel):
    """Payload for creating or updating a machine."""

    id: str | None = None
    name: str = Field(default="")
    mode: str = Field(default="local", description="local or ssh")
    enabled: bool = True
    services: list[str] = Field(default_factory=list)
    host: str = ""
    port: int = 22
    username: str = ""
    key_directory: str = ""
    key_name: str = ""
    ssh_key_id: str = ""
    ssh_private_key: str = ""
    ssh_private_key_passphrase: str = ""
    password: str = ""
    notes: str = ""


@router.get("/machines")
def get_machines(store: SettingsStore = Depends(get_settings_store)) -> list[dict[str, Any]]:
    return store.list_machines()


def _resolve_ssh_client(
    machine: MonitoringMachineInput,
    store: SettingsStore,
) -> tuple[RemoteSSHClient, str, int]:
    host = machine.host.strip()
    username = machine.username.strip()
    port = int(machine.port or 22)
    if not host or not username:
        raise HTTPException(status_code=400, detail="SSH machine is missing host or username")

    private_key = machine.ssh_private_key
    passphrase = machine.ssh_private_key_passphrase
    if machine.ssh_key_id:
        ssh_key = store.get_ssh_key(machine.ssh_key_id)
        if ssh_key:
            private_key = str(ssh_key.get("private_key") or private_key)
            passphrase = str(ssh_key.get("passphrase") or passphrase)

    key_filename = ""
    if machine.key_directory and machine.key_name:
        key_filename = f"{machine.key_directory}/{machine.key_name}"

    settings = get_settings()
    client = RemoteSSHClient(
        host=host,
        username=username,
        port=port,
        key_filename=key_filename or None,
        private_key=private_key or None,
        private_key_passphrase=passphrase or None,
        password=machine.password or None,
        known_hosts_path=str(settings.ssh_known_hosts_file),
    )
    return client, host, port


def _raise_ssh_validation_error(host: str, port: int, exc: Exception) -> None:
    message = str(exc)
    lowered = message.lower()
    if "protocol banner" in lowered:
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=(f"SSH banner not received from {host}:{port}; the backend could not complete the SSH handshake."),
        ) from exc
    if "no authentication methods available" in lowered or "authentication failed" in lowered:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail=(
                f"SSH authentication failed for {host}:{port}. "
                "Check the selected SSH key, passphrase, username, or password."
            ),
        ) from exc
    raise HTTPException(
        status_code=status.HTTP_502_BAD_GATEWAY,
        detail=f"SSH validation failed for {host}:{port}: {message}",
    ) from exc


def _validate_saved_machine_ssh(machine: MonitoringMachineInput, store: SettingsStore) -> None:
    if str(machine.mode or "").strip().lower() != "ssh":
        return
    client, host, port = _resolve_ssh_client(machine, store)
    try:
        client.connect()
    except Exception as exc:
        _raise_ssh_validation_error(host, port, exc)
    finally:
        client.close()


@router.post("/machines/test-ssh")
def test_machine_ssh(
    machine: MonitoringMachineInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    if str(machine.mode or "").strip().lower() != "ssh":
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="SSH validation only applies to SSH machines"
        )

    client, host, port = _resolve_ssh_client(machine, store)
    settings = get_settings()
    known_hosts_updated = not has_known_host(host, port, settings.ssh_known_hosts_file)

    try:
        client.connect()
    except Exception as exc:
        message = str(exc)
        lowered = message.lower()
        if "protocol banner" in lowered:
            raise HTTPException(
                status_code=status.HTTP_502_BAD_GATEWAY,
                detail=(
                    f"SSH banner not received from {host}:{port}; the backend recorded the host key, "
                    "but SSH auth could not be validated. Confirm the SSH service is running."
                ),
            ) from exc
        if "no authentication methods available" in lowered or "authentication failed" in lowered:
            raise HTTPException(
                status_code=status.HTTP_401_UNAUTHORIZED,
                detail=(
                    f"SSH banner received from {host}:{port}, but authentication failed. "
                    "Check the selected SSH key, passphrase, username, or password."
                ),
            ) from exc
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"SSH validation failed for {host}:{port}: {message}",
        ) from exc
    finally:
        client.close()

    return {
        "status": "ok",
        "message": (
            f"SSH connection succeeded for {host}:{port}; host key "
            f"{'was recorded' if known_hosts_updated else 'was already trusted'} and authentication worked."
        ),
        "host": host,
        "port": port,
        "known_hosts_updated": known_hosts_updated,
    }


@router.post("/machines", status_code=status.HTTP_201_CREATED)
def post_machine(
    machine: MonitoringMachineInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine.id)
    saved_machine = MonitoringMachineInput.model_validate(saved)
    _validate_saved_machine_ssh(saved_machine, store)
    return saved


@router.put("/machines/{machine_id}")
def put_machine(
    machine_id: str,
    machine: MonitoringMachineInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    if not store.get_machine(machine_id):
        raise HTTPException(status_code=404, detail="Machine not found")
    saved = store.upsert_machine(machine.model_dump(exclude_none=True), machine_id)
    saved_machine = MonitoringMachineInput.model_validate(saved)
    _validate_saved_machine_ssh(saved_machine, store)
    return saved


@router.delete("/machines/{machine_id}")
def delete_machine(machine_id: str, store: SettingsStore = Depends(get_settings_store)) -> dict[str, str]:
    if not store.get_machine(machine_id):
        raise HTTPException(status_code=404, detail="Machine not found")
    store.delete_machine(machine_id)
    return {"status": "deleted"}


class SSHKeyInput(BaseModel):
    id: str | None = None
    name: str = Field(default="")
    private_key: str = Field(default="")
    passphrase: str = Field(default="")
    public_key: str = Field(default="")
    fingerprint: str = Field(default="")
    notes: str = Field(default="")


class SSHKeyGenerateInput(BaseModel):
    name: str = Field(default="")
    passphrase: str = Field(default="")
    notes: str = Field(default="")
    bits: int = Field(default=4096, ge=2048, le=8192)


@router.post("/ssh-keys/generate")
def generate_ssh_key(
    payload: SSHKeyGenerateInput,
) -> dict[str, Any]:
    key = paramiko.RSAKey.generate(bits=payload.bits)
    private_buffer = StringIO()
    key.write_private_key(private_buffer, password=payload.passphrase or None)
    private_key = private_buffer.getvalue()
    public_key = f"{key.get_name()} {key.get_base64()}"
    fingerprint = ":".join(f"{b:02x}" for b in key.get_fingerprint())
    return {
        "name": payload.name,
        "private_key": private_key,
        "passphrase": payload.passphrase,
        "notes": payload.notes,
        "public_key": public_key,
        "fingerprint": fingerprint,
    }


@router.get("/ssh-keys")
def get_ssh_keys(store: SettingsStore = Depends(get_settings_store)) -> list[dict[str, Any]]:
    return store.list_ssh_keys()


@router.post("/ssh-keys", status_code=status.HTTP_201_CREATED)
def post_ssh_key(
    key: SSHKeyInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    return store.upsert_ssh_key(key.model_dump(exclude_none=True), key.id)


@router.put("/ssh-keys/{key_id}")
def put_ssh_key(
    key_id: str,
    key: SSHKeyInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    if not store.get_ssh_key(key_id):
        raise HTTPException(status_code=404, detail="SSH key not found")
    return store.upsert_ssh_key(key.model_dump(exclude_none=True), key_id)


@router.delete("/ssh-keys/{key_id}")
def delete_ssh_key(key_id: str, store: SettingsStore = Depends(get_settings_store)) -> dict[str, str]:
    if not store.get_ssh_key(key_id):
        raise HTTPException(status_code=404, detail="SSH key not found")
    store.delete_ssh_key(key_id)
    return {"status": "deleted"}


class ResetLocalDatabaseInput(BaseModel):
    confirm_phrase: str = Field(..., description="Must be RESET LOCAL DATABASE")
    acknowledge_settings_loss: bool = False
    acknowledge_media_index_loss: bool = False
    acknowledge_irreversible: bool = False


@router.post("/reset-local-database")
def reset_local_database(
    payload: ResetLocalDatabaseInput,
    store: SettingsStore = Depends(get_settings_store),
) -> dict[str, Any]:
    expected = "RESET LOCAL DATABASE"
    if payload.confirm_phrase.strip().upper() != expected:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Confirmation phrase does not match")
    if not (
        payload.acknowledge_settings_loss and payload.acknowledge_media_index_loss and payload.acknowledge_irreversible
    ):
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST, detail="All confirmation checkboxes must be selected"
        )

    settings_removed = remove_sqlite_database(store.db_path)
    media_index = MediaIndex()
    media_removed = remove_sqlite_database(media_index.db_path)

    return {
        "status": "reset",
        "settings_db_removed": bool(settings_removed),
        "media_index_removed": bool(media_removed),
        "settings_files": settings_removed,
        "media_index_files": media_removed,
    }
