"""Orchestration boundary for OLT discovery and MikroTik PPPoE provisioning.

Hardware is never contacted by default.  Callers must inject explicit adapters;
MockOLTAdapter/MockMikrotikAdapter are safe in-memory alternatives.
"""
from dataclasses import dataclass
from datetime import datetime, timezone

from app.models.models import Customer
from app.core.olt_adapters import get_olt_adapter, UnsupportedOLTError, ONTNotFoundError
from app.core.mikrotik import MikrotikAdapter, MikrotikProvisioningError
from app.core.customer_identity import decrypt_pppoe_password


@dataclass
class ProvisioningResult:
    mikrotik_status: str
    olt_status: str
    message: str


async def provision_customer(
    customer: Customer,
    *,
    olt_adapter=None,
    mikrotik_adapter: MikrotikAdapter | None = None,
    used_onu_ids: set[int] | None = None,
) -> ProvisioningResult:
    """Discover/prepare both targets without implying hardware success.

    ``olt_adapter`` and ``mikrotik_adapter`` are explicit dependencies.  With
    neither supplied this function only validates configuration and reports
    ``not_configured``; it does not queue a false success or open a connection.
    """
    missing = []
    if not customer.mikrotik_user:
        missing.append("user MikroTik")
    if not customer.ont_sn:
        missing.append("SN ONT")
    if missing:
        return ProvisioningResult(
            mikrotik_status="blocked" if "user MikroTik" in missing else "pending",
            olt_status="blocked" if "SN ONT" in missing else "pending",
            message="Data belum lengkap: " + ", ".join(missing) + ".",
        )

    if olt_adapter is None and (customer.olt_vendor or customer.olt_model):
        try:
            get_olt_adapter(customer.olt_vendor, customer.olt_model)
        except UnsupportedOLTError as exc:
            return ProvisioningResult("not_configured", "unsupported", str(exc))

    olt_status = "not_configured"
    messages = []
    if olt_adapter is not None:
        try:
            discovery = olt_adapter.discover_ont(customer.ont_sn)
            customer.ont_brand = discovery.brand
            customer.olt_port = discovery.port
            if customer.onu_id is None:
                customer.onu_id = olt_adapter.allocate_onu_id(discovery.port, used_onu_ids)
            olt_status = "simulated" if olt_adapter.__class__.__name__.startswith("Mock") else "discovered"
            messages.append(f"ONT ditemukan: {discovery.brand} pada port {discovery.port}, ONU ID {customer.onu_id}.")
        except (ONTNotFoundError, ValueError, RuntimeError) as exc:
            return ProvisioningResult("not_configured", "failed", f"Discovery OLT gagal: {exc}")
    else:
        messages.append("Adapter OLT hardware belum diberikan; discovery tidak dijalankan.")

    mikrotik_status = "not_configured"
    if mikrotik_adapter is not None:
        if not customer.pppoe_username or not customer.pppoe_password:
            return ProvisioningResult("blocked", olt_status, "Username dan password PPPoE wajib untuk MikroTik.")
        try:
            mikrotik_adapter.upsert_pppoe_secret(
                customer.pppoe_username, decrypt_pppoe_password(customer.pppoe_password), customer.service_profile
            )
            mikrotik_status = "simulated" if mikrotik_adapter.__class__.__name__.startswith("Mock") else "provisioned"
            messages.append(f"Secret PPPoE {customer.pppoe_username} dibuat/diperbarui.")
        except MikrotikProvisioningError as exc:
            return ProvisioningResult("failed", olt_status, f"Provisioning MikroTik gagal: {exc}")
    else:
        messages.append("Adapter MikroTik hardware belum diberikan; secret PPPoE tidak dikirim.")

    return ProvisioningResult(mikrotik_status, olt_status, " ".join(messages))


def _set_customer_pppoe_enabled(customer: Customer, adapter: MikrotikAdapter, enabled: bool) -> None:
    if not customer.pppoe_username:
        raise MikrotikProvisioningError("Username PPPoE belum diisi")
    adapter.set_pppoe_secret_enabled(customer.pppoe_username, enabled)


def suspend_customer_pppoe(customer: Customer, adapter: MikrotikAdapter) -> None:
    """Suspend only through an explicitly supplied adapter."""
    _set_customer_pppoe_enabled(customer, adapter, False)
    customer.status = "suspended"
    customer.provisioning_message = "Secret PPPoE dinonaktifkan melalui adapter MikroTik."


def unsuspend_customer_pppoe(customer: Customer, adapter: MikrotikAdapter) -> None:
    _set_customer_pppoe_enabled(customer, adapter, True)
    customer.status = "active"
    customer.provisioning_message = "Secret PPPoE diaktifkan kembali melalui adapter MikroTik."


async def mark_provisioned(customer: Customer, db: AsyncSession, message: str | None = None) -> None:
    """Use only after real adapters independently confirm both targets."""
    customer.provisioning_status = "provisioned"
    customer.mikrotik_provisioning_status = "provisioned"
    customer.olt_provisioning_status = "provisioned"
    if message:
        customer.provisioning_message = message
    customer.provisioned_at = datetime.now(timezone.utc)
    customer.status = "active"
    await db.commit()
