"""Automatic subscriber access synchronization owned by the billing module.

Billing remains usable when no device is configured, but every payment/suspend
operation attempts the assigned MikroTik directly when credentials are present.
Secrets are decrypted only in memory and are never returned to templates/logs.
"""
from dataclasses import dataclass
import asyncio

from sqlalchemy.ext.asyncio import AsyncSession

from app.core.customer_identity import decrypt_pppoe_password
from app.core.device_credentials import decrypt_secret as decrypt_device_secret
from app.core.secrets import decrypt_secret as decrypt_legacy_secret
from app.core.mikrotik import RealMikrotikAdapter, MikrotikProvisioningError
from app.models.models import Customer, DeviceCredential, MikrotikConfig, MikrotikDevice
from sqlalchemy import select


@dataclass
class AccessSyncResult:
    status: str
    message: str


async def _adapter_for_customer(customer: Customer, db: AsyncSession):
    """Resolve assigned device first, then the billing-wide legacy config."""
    mikrotik_device_id = getattr(customer, "mikrotik_device_id", None)
    if mikrotik_device_id:
        device = await db.get(MikrotikDevice, mikrotik_device_id)
        if not device or not device.is_active or not device.credential_id:
            return None, "Device MikroTik pelanggan belum aktif atau credential belum ditetapkan."
        credential = await db.get(DeviceCredential, device.credential_id)
        if not credential or not credential.is_active:
            return None, "Credential MikroTik pelanggan belum aktif."
        return RealMikrotikAdapter(
            host=device.host,
            username=credential.username,
            password=decrypt_device_secret(credential.encrypted_secret),
            api_port=device.api_port or 8728,
        ), None

    config = (await db.execute(select(MikrotikConfig).limit(1))).scalar_one_or_none()
    if not config or not config.host or not config.username or not config.password:
        return None, "MikroTik belum dikonfigurasi di Billing. Pembayaran tersimpan; sinkronisasi menunggu konfigurasi."
    return RealMikrotikAdapter(
        host=config.host,
        username=config.username,
        password=decrypt_legacy_secret(config.password),
        api_port=config.api_port or 8728,
    ), None


async def sync_customer_access(customer: Customer, db: AsyncSession, *, enabled: bool) -> AccessSyncResult:
    """Enable/disable a PPPoE secret as a billing side effect."""
    if not customer.pppoe_username or not customer.pppoe_password:
        return AccessSyncResult("blocked", "Data PPPoE pelanggan belum lengkap; sinkronisasi tidak dijalankan.")
    try:
        adapter, problem = await _adapter_for_customer(customer, db)
    except Exception:
        return AccessSyncResult("failed", "Credential MikroTik tidak dapat dibaca; periksa konfigurasi Billing.")
    if problem:
        return AccessSyncResult("pending", problem)
    try:
        password = decrypt_pppoe_password(customer.pppoe_password)
        if enabled:
            profile, fallback = await asyncio.to_thread(adapter.resolve_profile, customer.service_profile)
            await asyncio.to_thread(adapter.upsert_pppoe_secret, customer.pppoe_username, password, profile)
            suffix = " Profile default dipakai karena profile paket tidak ditemukan." if fallback else ""
            return AccessSyncResult("synced", f"Akses PPPoE diaktifkan otomatis dari Billing.{suffix}")
        await asyncio.to_thread(adapter.set_pppoe_secret_enabled, customer.pppoe_username, False)
        return AccessSyncResult("synced", "Akses PPPoE dinonaktifkan otomatis dari Billing.")
    except MikrotikProvisioningError as exc:
        return AccessSyncResult("failed", f"Sinkronisasi MikroTik gagal: {str(exc)[:300]}")
    except Exception:
        return AccessSyncResult("failed", "Sinkronisasi MikroTik gagal karena kesalahan internal.")
