# @File: backend/routers/smtp_settings.py
# @Desc: Admin-only CRUD for SMTP credentials plus a "send test email" action.
#        The settings row is effectively a singleton (we always read/write id=1
#        worth of state via an upsert). Reads don't require the row to exist —
#        we return an "empty" response in that case so the CMS page can render
#        cleanly on a fresh install.
import logging
from typing import Optional

from dependencies.auth import get_admin_user
from dependencies.database import get_db
from fastapi import APIRouter, Depends, HTTPException, status
from models.smtp_settings import SmtpSettings
from schemas.auth import UserResponse
from schemas.smtp_settings import (
    SmtpSettingsResponse,
    SmtpSettingsUpdate,
    SmtpTestRequest,
    SmtpTestResponse,
)
from services import email_sender
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

logger = logging.getLogger(__name__)

router = APIRouter(prefix="/api/v1/admin/smtp-settings", tags=["admin-smtp-settings"])


async def _get_or_create_row(db: AsyncSession) -> SmtpSettings:
    """Return the active SMTP settings row, creating an empty one if needed.

    Only one row is expected, but we still use ``order_by(id)`` + ``limit(1)``
    so a historical duplicate would not break reads.
    """
    result = await db.execute(select(SmtpSettings).order_by(SmtpSettings.id.asc()).limit(1))
    row: Optional[SmtpSettings] = result.scalar_one_or_none()
    if row is None:
        row = SmtpSettings()
        db.add(row)
        await db.commit()
        await db.refresh(row)
    return row


def _to_response(row: SmtpSettings) -> SmtpSettingsResponse:
    return SmtpSettingsResponse(
        smtp_host=row.smtp_host,
        smtp_port=row.smtp_port,
        smtp_username=row.smtp_username,
        smtp_encryption=(row.smtp_encryption or "TLS"),  # type: ignore[arg-type]
        sender_name=row.sender_name,
        sender_email=row.sender_email,
        reply_to=row.reply_to,
        evt_new_ticket=row.evt_new_ticket,
        evt_invoice=row.evt_invoice,
        evt_service_up=row.evt_service_up,
        evt_service_down=row.evt_service_down,
        evt_user_register=row.evt_user_register,
        evt_payment_fail=row.evt_payment_fail,
        has_password=bool(row.smtp_password),
    )


@router.get("", response_model=SmtpSettingsResponse)
async def get_smtp_settings(
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> SmtpSettingsResponse:
    """Return the current SMTP settings. Password is never echoed."""
    row = await _get_or_create_row(db)
    return _to_response(row)


@router.put("", response_model=SmtpSettingsResponse)
async def update_smtp_settings(
    payload: SmtpSettingsUpdate,
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> SmtpSettingsResponse:
    """Upsert SMTP settings. An empty/missing ``smtp_password`` preserves the existing value."""
    row = await _get_or_create_row(db)

    row.smtp_host = payload.smtp_host
    row.smtp_port = payload.smtp_port
    row.smtp_username = payload.smtp_username
    row.smtp_encryption = payload.smtp_encryption
    row.sender_name = payload.sender_name
    row.sender_email = str(payload.sender_email) if payload.sender_email else None
    row.reply_to = str(payload.reply_to) if payload.reply_to else None
    row.evt_new_ticket = payload.evt_new_ticket
    row.evt_invoice = payload.evt_invoice
    row.evt_service_up = payload.evt_service_up
    row.evt_service_down = payload.evt_service_down
    row.evt_user_register = payload.evt_user_register
    row.evt_payment_fail = payload.evt_payment_fail

    # Only replace the password if the admin actually typed something new.
    if payload.smtp_password is not None and payload.smtp_password != "":
        row.smtp_password = payload.smtp_password

    await db.commit()
    await db.refresh(row)
    # Invalidate the in-process cache so the next send uses the new values.
    email_sender.invalidate_config_cache()
    return _to_response(row)


@router.post("/test", response_model=SmtpTestResponse)
async def send_test_email(
    payload: SmtpTestRequest,
    _admin: UserResponse = Depends(get_admin_user),
    db: AsyncSession = Depends(get_db),
) -> SmtpTestResponse:
    """Send a one-off test email using the currently saved SMTP settings."""
    # Make sure the in-memory cache reflects the very latest DB values.
    email_sender.invalidate_config_cache()
    try:
        await email_sender.send_email(
            to_addrs=[payload.to_email],
            subject="DoItEasy SMTP test email",
            text_body=(
                "This is a test email from the DoItEasy admin panel. "
                "If you are reading this, your SMTP configuration is working."
            ),
            html_body=(
                "<p>This is a test email from the <strong>DoItEasy admin panel</strong>.</p>"
                "<p>If you are reading this, your SMTP configuration is working.</p>"
            ),
        )
    except email_sender.SmtpConfigError as exc:
        logger.warning("SMTP test failed — configuration error: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail=str(exc),
        ) from exc
    except Exception as exc:  # pragma: no cover - network failure path
        logger.exception("SMTP test failed")
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"Failed to send test email: {exc}",
        ) from exc

    return SmtpTestResponse(success=True, message=f"Test email sent to {payload.to_email}.")