from datetime import datetime, timezone, timedelta
from fastapi import APIRouter, Request, Form, Depends
from fastapi.responses import HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from app.core.database import get_db
from app.core.security import require_roles
from app.models.models import Customer, Invoice, Payment
from app.models.models import MikrotikConfig
from app.core.billing_access import sync_customer_access
from app.core.secrets import encrypt_secret
import os

router = APIRouter(dependencies=[Depends(require_roles("admin_cs"))])
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
templates = Jinja2Templates(directory=os.path.join(BASE_DIR, "app/templates"))


def period_due(period: str) -> datetime:
    year, month = (int(x) for x in period.split("-"))
    return datetime(year, month, 10, tzinfo=timezone.utc)


@router.get("/billing", response_class=HTMLResponse)
async def billing_page(request: Request, db: AsyncSession = Depends(get_db)):
    invoices = (await db.execute(select(Invoice).options(selectinload(Invoice.customer)).order_by(Invoice.due_date.desc(), Invoice.id.desc()))).scalars().all()
    config = (await db.execute(select(MikrotikConfig).limit(1))).scalar_one_or_none()
    return templates.TemplateResponse(request=request, name="billing.html", context={"invoices": invoices, "mikrotik_config": config})


@router.post("/billing/mikrotik")
async def save_billing_mikrotik(
    host: str = Form(...), username: str = Form(...), password: str = Form(""),
    api_port: int = Form(8728), db: AsyncSession = Depends(get_db),
):
    """Configure the RouterOS target from Billing; no second application needed."""
    if not host.strip() or not username.strip() or not 1 <= api_port <= 65535:
        return RedirectResponse(url="/billing?config_error=1", status_code=303)
    config = (await db.execute(select(MikrotikConfig).limit(1))).scalar_one_or_none()
    if config:
        config.host, config.username, config.api_port = host.strip(), username.strip(), api_port
        if password:
            config.password = encrypt_secret(password)
    else:
        if not password:
            return RedirectResponse(url="/billing?config_error=1", status_code=303)
        db.add(MikrotikConfig(host=host.strip(), username=username.strip(), api_port=api_port, password=encrypt_secret(password)))
    await db.commit()
    return RedirectResponse(url="/billing?configured=1", status_code=303)


@router.post("/billing/generate")
async def generate_billing(period: str = Form(...), db: AsyncSession = Depends(get_db)):
    due_date = period_due(period)
    customers = (await db.execute(select(Customer).where(Customer.type == "bulanan_ppp"))).scalars().all()
    created = 0
    for customer in customers:
        exists = await db.scalar(select(Invoice).where(Invoice.customer_id == customer.id, Invoice.period == period))
        if not exists:
            db.add(Invoice(customer_id=customer.id, period=period, amount=customer.harga_paket or 0, due_date=due_date))
            created += 1
    await db.commit()
    return RedirectResponse(url=f"/billing?created={created}", status_code=303)


@router.post("/billing/{invoice_id}/pay")
async def pay_invoice(invoice_id: int, method: str = Form("cash"), reference: str = Form(None), db: AsyncSession = Depends(get_db)):
    invoice = await db.get(Invoice, invoice_id)
    if invoice and invoice.status != "paid":
        invoice.status = "paid"
        invoice.paid_at = datetime.now(timezone.utc)
        customer = await db.get(Customer, invoice.customer_id)
        if customer:
            customer.is_paid = True
            sync = await sync_customer_access(customer, db, enabled=True)
            customer.provisioning_message = sync.message
            customer.mikrotik_provisioning_status = sync.status
            if customer.status == "suspended" and sync.status != "failed":
                customer.status = "active"
        db.add(Payment(customer_id=invoice.customer_id, amount=invoice.amount, period=invoice.period, method=method, reference=reference))
        await db.commit()
    return RedirectResponse(url="/billing", status_code=303)


@router.post("/billing/{invoice_id}/suspend")
async def suspend_invoice_customer(invoice_id: int, db: AsyncSession = Depends(get_db)):
    invoice = await db.get(Invoice, invoice_id)
    due = invoice.due_date if invoice else None
    if due and due.tzinfo is None:
        due = due.replace(tzinfo=timezone.utc)
    if invoice and invoice.status != "paid" and due and due <= datetime.now(timezone.utc):
        customer = await db.get(Customer, invoice.customer_id)
        if customer:
            customer.status = "suspended"
            customer.is_paid = False
            sync = await sync_customer_access(customer, db, enabled=False)
            customer.provisioning_message = f"Suspended karena invoice {invoice.period} belum dibayar. {sync.message}"
            customer.mikrotik_provisioning_status = sync.status
            await db.commit()
    return RedirectResponse(url="/billing", status_code=303)


@router.post("/billing/{invoice_id}/sync")
async def sync_invoice_access(invoice_id: int, db: AsyncSession = Depends(get_db)):
    """Retry the exact access state from Billing after device configuration."""
    invoice = await db.get(Invoice, invoice_id)
    if invoice:
        customer = await db.get(Customer, invoice.customer_id)
        if customer:
            sync = await sync_customer_access(customer, db, enabled=invoice.status == "paid")
            customer.provisioning_message = sync.message
            customer.mikrotik_provisioning_status = sync.status
            if sync.status != "failed" and invoice.status == "paid":
                customer.status, customer.is_paid = "active", True
            await db.commit()
    return RedirectResponse(url="/billing", status_code=303)
