from pathlib import Path
from uuid import uuid4

from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
from fastapi.responses import FileResponse
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from app.core.database import get_db
from app.core.models import SystemConfig, User
from app.core.security import require_permission
from app.services.email_service import send_email
from app.services.invoice_pdf import (
    AVAILABLE_TEMPLATE_VARIABLES,
    DEFAULT_INVOICE_TEMPLATE,
    DEFAULT_INVOICE_EMAIL_BODY_TEMPLATE,
    DEFAULT_INVOICE_EMAIL_SUBJECT_TEMPLATE,
    DEFAULT_PAYMENT_METHODS,
)
from app.core.config import get_ai_config as runtime_ai_config

router = APIRouter(prefix="/api/config", tags=["config"])
BRANDING_UPLOAD_ROOT = Path(__file__).resolve().parents[2] / "storage" / "branding"
BRANDING_UPLOAD_ROOT.mkdir(parents=True, exist_ok=True)


# ===== Helpers =====

def _get_secret_masked(db: Session, key_name: str) -> str | None:
    row = db.get(SystemConfig, key_name)
    if not row:
        return None
    if row.is_secret and row.key_value:
        return "******"
    return row.key_value


def _upsert_secret(
    db: Session,
    key_name: str,
    value: str,
    is_secret: bool = False,
    *,
    commit: bool = True,
):
    row = db.get(SystemConfig, key_name)
    if row:
        row.key_value = value
        row.is_secret = is_secret
    else:
        row = SystemConfig(key_name=key_name, key_value=value, is_secret=is_secret)
        db.add(row)
    if commit:
        db.commit()


def _get_config_value(db: Session, key_name: str, default: str = "") -> str:
    row = db.get(SystemConfig, key_name)
    if row and row.key_value is not None:
        return row.key_value
    return default


# ===== System Basics =====

SYSTEM_DEFAULTS = {
    "system_name": "ERP Control",
    "system_subtitle": "IT service operations",
    "company_name": "IT Service Company",
    "default_currency": "NZD",
    "timezone": "Pacific/Auckland",
    "date_format": "YYYY-MM-DD",
}
SYSTEM_LOGO_PATH_KEY = "system_logo_path"
SYSTEM_LOGO_FILENAME_KEY = "system_logo_filename"
SYSTEM_LOGO_MIME_KEY = "system_logo_mime"
ALLOWED_LOGO_MIME_TYPES = {
    "image/png": ".png",
    "image/jpeg": ".jpg",
    "image/webp": ".webp",
    "image/svg+xml": ".svg",
}


class SystemSettingsResponse(BaseModel):
    system_name: str
    system_subtitle: str
    company_name: str
    default_currency: str
    timezone: str
    date_format: str
    logo_url: str | None = None


class SystemSettingsUpdate(BaseModel):
    system_name: str = Field(min_length=1, max_length=80)
    system_subtitle: str = Field(max_length=120)
    company_name: str = Field(max_length=120)
    default_currency: str = Field(min_length=3, max_length=10)
    timezone: str = Field(max_length=80)
    date_format: str = Field(max_length=40)


def _get_system_settings(db: Session) -> SystemSettingsResponse:
    logo_url = None
    logo_path = _get_config_value(db, SYSTEM_LOGO_PATH_KEY)
    if logo_path:
        path = Path(logo_path)
        if path.exists():
            logo_url = f"/api/config/system/logo?v={path.stat().st_mtime_ns}"

    return SystemSettingsResponse(
        logo_url=logo_url,
        **{
            key: _get_config_value(db, key, default)
            for key, default in SYSTEM_DEFAULTS.items()
        },
    )


@router.get("/system", response_model=SystemSettingsResponse)
def get_system_settings(
    db: Session = Depends(get_db),
):
    return _get_system_settings(db)


@router.put("/system", response_model=SystemSettingsResponse)
def update_system_settings(
    data: SystemSettingsUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    for key, value in data.model_dump().items():
        _upsert_secret(db, key, value.strip(), is_secret=False, commit=False)
    db.commit()
    return _get_system_settings(db)


def _delete_logo_file(path_str: str | None) -> None:
    if not path_str:
        return
    path = Path(path_str)
    if path.exists():
        path.unlink()


def _clear_system_logo_config(db: Session) -> None:
    old_path = _get_config_value(db, SYSTEM_LOGO_PATH_KEY)
    for key_name in (SYSTEM_LOGO_PATH_KEY, SYSTEM_LOGO_FILENAME_KEY, SYSTEM_LOGO_MIME_KEY):
        row = db.get(SystemConfig, key_name)
        if row:
            db.delete(row)
    db.commit()
    _delete_logo_file(old_path)


@router.post("/system/logo", response_model=SystemSettingsResponse)
async def upload_system_logo(
    file: UploadFile = File(...),
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    content_type = (file.content_type or "").lower()
    suffix = ALLOWED_LOGO_MIME_TYPES.get(content_type)
    if not suffix:
        raise HTTPException(
            status_code=400,
            detail="Unsupported logo format. Please upload PNG, JPG, WEBP, or SVG.",
        )

    file_bytes = await file.read()
    if not file_bytes:
        raise HTTPException(status_code=400, detail="Logo file is empty.")
    if len(file_bytes) > 2 * 1024 * 1024:
        raise HTTPException(status_code=400, detail="Logo file must be 2MB or smaller.")

    old_path = _get_config_value(db, SYSTEM_LOGO_PATH_KEY)

    target = BRANDING_UPLOAD_ROOT / f"system-logo-{uuid4().hex}{suffix}"
    target.write_bytes(file_bytes)

    try:
        _upsert_secret(db, SYSTEM_LOGO_PATH_KEY, str(target), is_secret=False, commit=False)
        _upsert_secret(db, SYSTEM_LOGO_FILENAME_KEY, file.filename or target.name, is_secret=False, commit=False)
        _upsert_secret(db, SYSTEM_LOGO_MIME_KEY, content_type, is_secret=False, commit=False)
        db.commit()
    except Exception:
        db.rollback()
        _delete_logo_file(str(target))
        raise

    _delete_logo_file(old_path)
    return _get_system_settings(db)


@router.delete("/system/logo", response_model=SystemSettingsResponse)
def delete_system_logo(
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    _clear_system_logo_config(db)
    return _get_system_settings(db)


@router.get("/system/logo")
def get_system_logo(
    db: Session = Depends(get_db),
):
    logo_path = _get_config_value(db, SYSTEM_LOGO_PATH_KEY)
    if not logo_path:
        raise HTTPException(status_code=404, detail="System logo not configured")

    path = Path(logo_path)
    if not path.exists():
        raise HTTPException(status_code=404, detail="System logo file not found")

    media_type = _get_config_value(db, SYSTEM_LOGO_MIME_KEY, "application/octet-stream")
    filename = _get_config_value(db, SYSTEM_LOGO_FILENAME_KEY, path.name)
    return FileResponse(path=path, media_type=media_type, filename=filename)


# ===== SMTP =====

class SMTPConfigResponse(BaseModel):
    model_config = {"from_attributes": True}

    smtp_host: str | None = None
    smtp_port: int | None = None
    smtp_user: str | None = None
    smtp_password: str | None = None
    smtp_from_name: str | None = None
    smtp_from_email: str | None = None
    smtp_use_tls: bool | None = None


class SMTPConfigUpdate(BaseModel):
    smtp_host: str | None = None
    smtp_port: int | None = None
    smtp_user: str | None = None
    smtp_password: str | None = None
    smtp_from_name: str | None = None
    smtp_from_email: str | None = None
    smtp_use_tls: bool | None = None


class SMTPTestRequest(BaseModel):
    to_email: str


smtp_keys = ["smtp_host", "smtp_port", "smtp_user", "smtp_password",
             "smtp_from_name", "smtp_from_email", "smtp_use_tls"]


@router.get("/smtp", response_model=SMTPConfigResponse)
def get_smtp_config(
    current_user: User = Depends(require_permission("settings.read")),
    db: Session = Depends(get_db),
):
    result = {}
    for key in smtp_keys:
        if key == "smtp_use_tls":
            raw = _get_secret_masked(db, "smtp_use_tls")
            result["smtp_use_tls"] = raw.lower() == "true" if raw else True
        elif key == "smtp_port":
            raw = _get_secret_masked(db, key)
            result["smtp_port"] = int(raw) if raw else None
        else:
            result[key] = _get_secret_masked(db, key)
    return SMTPConfigResponse(**result)


@router.put("/smtp", response_model=SMTPConfigResponse)
def update_smtp_config(
    data: SMTPConfigUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    update_map = data.model_dump(exclude_unset=True)
    for key, value in update_map.items():
        is_secret = key == "smtp_password"
        if key == "smtp_use_tls":
            value = "true" if value else "false"
        if key == "smtp_port" and value is not None:
            value = str(value)
        _upsert_secret(
            db,
            key,
            str(value) if value is not None else "",
            is_secret=is_secret,
            commit=False,
        )
    db.commit()
    return get_smtp_config(current_user=current_user, db=db)


@router.post("/smtp/test")
async def test_smtp_config(
    data: SMTPTestRequest,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    result = await send_email(
        to_email=data.to_email,
        subject="ERP SMTP Test",
        body="This is a test email from ERP.",
    )
    if not result["success"]:
        raise HTTPException(status_code=400, detail=result["error"])
    return {"success": True, "message": "Test email sent"}


# ===== Telegram =====

class TelegramConfigResponse(BaseModel):
    bot_token: str | None
    chat_id: str | None = None


class TelegramConfigUpdate(BaseModel):
    bot_token: str | None = None
    chat_id: str | None = None


@router.get("/telegram", response_model=TelegramConfigResponse)
def get_telegram_config(
    current_user: User = Depends(require_permission("settings.read")),
    db: Session = Depends(get_db),
):
    return TelegramConfigResponse(
        bot_token=_get_secret_masked(db, "telegram_bot_token"),
        chat_id=_get_config_value(db, "telegram_chat_id"),
    )


@router.put("/telegram", response_model=TelegramConfigResponse)
def update_telegram_config(
    data: TelegramConfigUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    if data.bot_token is not None:
        _upsert_secret(db, "telegram_bot_token", data.bot_token, is_secret=True, commit=False)
    if data.chat_id is not None:
        _upsert_secret(db, "telegram_chat_id", data.chat_id, is_secret=False, commit=False)
    db.commit()
    return get_telegram_config(current_user=current_user, db=db)


# ===== AI =====

class AIConfigResponse(BaseModel):
    ai_model: str | None = None
    ai_api_key: str | None = None


class AIConfigUpdate(BaseModel):
    ai_model: str | None = None
    ai_api_key: str | None = None


@router.get("/ai", response_model=AIConfigResponse)
def get_ai_config(
    current_user: User = Depends(require_permission("settings.read")),
    db: Session = Depends(get_db),
):
    config = runtime_ai_config()
    api_key = _get_secret_masked(db, "ai_api_key")
    return AIConfigResponse(
        ai_model=config["model"],
        ai_api_key=api_key,
    )


@router.put("/ai", response_model=AIConfigResponse)
def update_ai_config(
    data: AIConfigUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    if data.ai_model is not None:
        _upsert_secret(db, "ai_model", data.ai_model, is_secret=False, commit=False)
    if data.ai_api_key is not None:
        _upsert_secret(db, "ai_api_key", data.ai_api_key, is_secret=True, commit=False)
    db.commit()
    return get_ai_config(current_user=current_user, db=db)


# ===== Invoice Template =====

class InvoiceTemplateConfigResponse(BaseModel):
    template_html: str
    email_subject_template: str
    email_body_template: str
    available_variables: list[str] = Field(default_factory=list)


class InvoiceTemplateConfigUpdate(BaseModel):
    template_html: str
    email_subject_template: str
    email_body_template: str


@router.get("/invoice-template", response_model=InvoiceTemplateConfigResponse)
def get_invoice_template_config(
    current_user: User = Depends(require_permission("settings.read")),
    db: Session = Depends(get_db),
):
    return InvoiceTemplateConfigResponse(
        template_html=_get_config_value(db, "invoice_template_html", DEFAULT_INVOICE_TEMPLATE),
        email_subject_template=_get_config_value(
            db,
            "invoice_email_subject_template",
            DEFAULT_INVOICE_EMAIL_SUBJECT_TEMPLATE,
        ),
        email_body_template=_get_config_value(
            db,
            "invoice_email_body_template",
            DEFAULT_INVOICE_EMAIL_BODY_TEMPLATE,
        ),
        available_variables=list(AVAILABLE_TEMPLATE_VARIABLES),
    )


@router.put("/invoice-template", response_model=InvoiceTemplateConfigResponse)
def update_invoice_template_config(
    data: InvoiceTemplateConfigUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    _upsert_secret(db, "invoice_template_html", data.template_html, is_secret=False, commit=False)
    _upsert_secret(
        db,
        "invoice_email_subject_template",
        data.email_subject_template,
        is_secret=False,
        commit=False,
    )
    _upsert_secret(
        db,
        "invoice_email_body_template",
        data.email_body_template,
        is_secret=False,
        commit=False,
    )
    db.commit()
    return get_invoice_template_config(current_user=current_user, db=db)


# ===== Payment Methods =====

class PaymentMethodsConfigResponse(BaseModel):
    payment_methods: list[str] = Field(default_factory=list)


class PaymentMethodsConfigUpdate(BaseModel):
    payment_methods: list[str] = Field(default_factory=list)


def _normalize_payment_methods(methods: list[str]) -> list[str]:
    normalized: list[str] = []
    for method in methods:
        value = method.strip()
        if value:
            normalized.append(value)
    return normalized or list(DEFAULT_PAYMENT_METHODS)


@router.get("/payment-methods", response_model=PaymentMethodsConfigResponse)
def get_payment_methods_config(
    current_user: User = Depends(require_permission("settings.read")),
    db: Session = Depends(get_db),
):
    raw = _get_config_value(db, "invoice_payment_methods", ",".join(DEFAULT_PAYMENT_METHODS))
    methods = _normalize_payment_methods(
        [part for chunk in raw.splitlines() for part in chunk.split(",")]
    )
    return PaymentMethodsConfigResponse(payment_methods=methods)


@router.put("/payment-methods", response_model=PaymentMethodsConfigResponse)
def update_payment_methods_config(
    data: PaymentMethodsConfigUpdate,
    current_user: User = Depends(require_permission("settings.write")),
    db: Session = Depends(get_db),
):
    methods = _normalize_payment_methods(data.payment_methods)
    _upsert_secret(
        db,
        "invoice_payment_methods",
        "\n".join(methods),
        is_secret=False,
        commit=False,
    )
    db.commit()
    return PaymentMethodsConfigResponse(payment_methods=methods)
