import asyncio
import ipaddress
import json
import socket
import time
from fastapi import APIRouter, Depends, HTTPException, Query
from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session
from typing import List

from database import get_db
from models.printer import Printer
from schemas.printer import PrinterCreate, PrinterUpdate, PrinterOut
from routers.deps import get_current_user, require_manager, require_sysadmin
from models.user import User
from models.product import Category, Product
from models.table import Table, TableGroup
from services import printer_service
from services.cloud_sync import _sync_once
from middleware.license_check import license_state
from config import settings

router = APIRouter()

_start_time = time.time()


@router.get("/health")
def health():
    return {"status": "ok", "version": settings.VERSION}


@router.get("/status")
def system_status(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    from datetime import datetime, timezone
    printers = db.query(Printer).filter(Printer.is_active == True).all()
    printer_statuses = []
    for p in printers:
        reachable = printer_service.check_printer(p.ip_address, p.port)
        printer_statuses.append({"id": p.id, "name": p.name, "reachable": reachable})

    licensed = license_state.get("licensed", True)
    locked = license_state.get("locked", False)
    lock_pending = license_state.get("lock_pending", False)
    expires_at = license_state.get("expires_at")
    days_until_expiry = license_state.get("days_until_expiry")
    grace_expires_at = license_state.get("grace_expires_at")

    # Determine lock_reason for the frontend banner logic
    # "admin"   — locked by sysadmin (immediately or deferred)
    # "expired" — license grace period over, site is blocked
    # None      — all good
    lock_reason = None
    if locked or lock_pending:
        lock_reason = "admin"
    elif not licensed:
        lock_reason = "expired"

    # Grace days remaining (only meaningful while in expiry grace period)
    grace_days_remaining = None
    if grace_expires_at:
        try:
            grace_dt = datetime.fromisoformat(grace_expires_at)
            if grace_dt.tzinfo is None:
                grace_dt = grace_dt.replace(tzinfo=timezone.utc)
            grace_days_remaining = max(0, (grace_dt - datetime.now(timezone.utc)).days)
        except ValueError:
            pass

    return {
        "uptime_seconds": int(time.time() - _start_time),
        "version": settings.VERSION,
        "latest_version": license_state.get("latest_version"),
        "licensed": licensed,
        "locked": locked,
        "lock_pending": lock_pending,
        "lock_reason": lock_reason,
        "expires_at": expires_at,
        "days_until_expiry": days_until_expiry,
        "grace_expires_at": grace_expires_at,
        "grace_days_remaining": grace_days_remaining,
        "sync_failed": license_state.get("sync_failed", False),
        "last_sync": license_state.get("last_sync"),
        "waiter_domain": license_state.get("waiter_domain"),
        "printers": printer_statuses,
    }


@router.post("/sync-license")
async def sync_license_now(user: User = Depends(require_manager)):
    """Trigger an immediate cloud heartbeat and return the fresh license state."""
    await _sync_once()
    return {
        "licensed": license_state.get("licensed", True),
        "locked": license_state.get("locked", False),
        "lock_pending": license_state.get("lock_pending", False),
        "lock_reason": (
            "admin" if (license_state.get("locked") or license_state.get("lock_pending"))
            else "expired" if not license_state.get("licensed", True)
            else None
        ),
        "expires_at": license_state.get("expires_at"),
        "days_until_expiry": license_state.get("days_until_expiry"),
        "sync_failed": license_state.get("sync_failed", False),
        "last_sync": license_state.get("last_sync"),
    }


@router.get("/printers", response_model=List[PrinterOut])
def list_printers(db: Session = Depends(get_db), user: User = Depends(require_manager)):
    return db.query(Printer).all()


@router.post("/printers", response_model=PrinterOut)
def create_printer(body: PrinterCreate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = Printer(**body.model_dump())
    db.add(printer)
    db.commit()
    db.refresh(printer)
    return printer


@router.post("/printers/test")
def test_printer(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = db.query(Printer).filter(Printer.id == printer_id).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")
    success, error = printer_service.send_test_print(printer.ip_address, printer.port, printer.name, printer.codepage_n)
    return {"success": success, "error": error}


@router.post("/printers/test-order")
def test_order_print(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = db.query(Printer).filter(Printer.id == printer_id).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")
    success, error = printer_service.send_test_order_print(printer.ip_address, printer.port, db, printer.line_width, printer.codepage_n)
    return {"success": success, "error": error}


@router.post("/printers/test-beep")
def test_beep(printer_id: int, n1: int = 2, n2: int = 2, n3: int = 1, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = db.query(Printer).filter(Printer.id == printer_id).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")
    success, error = printer_service.send_test_beep(printer.ip_address, printer.port, n1, n2, n3, printer.codepage_n)
    return {"success": success, "error": error}


@router.put("/printers/{printer_id}", response_model=PrinterOut)
def update_printer(printer_id: int, body: PrinterUpdate, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = db.query(Printer).filter(Printer.id == printer_id).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")
    for field, value in body.model_dump(exclude_none=True).items():
        setattr(printer, field, value)
    db.commit()
    db.refresh(printer)
    return printer


@router.delete("/printers/{printer_id}")
def delete_printer(printer_id: int, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    printer = db.query(Printer).filter(Printer.id == printer_id).first()
    if not printer:
        raise HTTPException(status_code=404, detail="Printer not found")
    db.delete(printer)
    db.commit()
    return {"ok": True}


def _detect_local_subnets() -> list[str]:
    """Return plausible /24 subnets based on local interface IPs, excluding loopback/docker."""
    subnets = []
    try:
        hostname = socket.gethostname()
        for info in socket.getaddrinfo(hostname, None):
            ip_str = info[4][0]
            try:
                addr = ipaddress.IPv4Address(ip_str)
                if addr.is_loopback or addr.is_link_local:
                    continue
                # Exclude docker bridge ranges (172.16-31.x.x)
                if addr.packed[0] == 172 and 16 <= addr.packed[1] <= 31:
                    continue
                net = str(ipaddress.IPv4Network(f"{ip_str}/24", strict=False))
                if net not in subnets:
                    subnets.append(net)
            except (ValueError, OSError):
                continue
    except OSError:
        pass
    return subnets


async def _check_port(ip: str, port: int, timeout: float) -> bool:
    try:
        _, writer = await asyncio.wait_for(
            asyncio.open_connection(ip, port), timeout=timeout
        )
        writer.close()
        try:
            await writer.wait_closed()
        except Exception:
            pass
        return True
    except Exception:
        return False


async def _scan_subnet_sse(subnet: str, port: int, token: str):
    """Async generator: yields SSE lines as printers are found."""
    try:
        network = ipaddress.IPv4Network(subnet, strict=False)
    except ValueError as e:
        yield f"data: {json.dumps({'error': str(e)})}\n\n"
        return

    hosts = list(network.hosts())
    semaphore = asyncio.Semaphore(50)  # max 50 concurrent connects

    async def check(ip_str: str):
        async with semaphore:
            return ip_str, await _check_port(ip_str, port, timeout=0.4)

    tasks = [asyncio.create_task(check(str(h))) for h in hosts]
    yield f"data: {json.dumps({'type': 'start', 'total': len(tasks), 'subnet': subnet, 'port': port})}\n\n"

    done_count = 0
    for coro in asyncio.as_completed(tasks):
        ip_str, reachable = await coro
        done_count += 1
        if reachable:
            yield f"data: {json.dumps({'type': 'found', 'ip': ip_str, 'port': port})}\n\n"
        if done_count % 20 == 0 or done_count == len(tasks):
            yield f"data: {json.dumps({'type': 'progress', 'done': done_count, 'total': len(tasks)})}\n\n"

    yield f"data: {json.dumps({'type': 'done'})}\n\n"


@router.get("/printers/scan")
async def scan_printers(
    subnet: str = Query(default=""),
    port: int = Query(default=9100),
    user: User = Depends(require_manager),
):
    """SSE endpoint: scan a subnet for devices responding on the given port."""
    # Fall back to auto-detected subnet if none provided
    if not subnet:
        detected = _detect_local_subnets()
        subnet = detected[0] if detected else "192.168.1.0/24"

    return StreamingResponse(
        _scan_subnet_sse(subnet, port, ""),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "X-Accel-Buffering": "no",
        },
    )


@router.get("/printers/scan-hints")
def scan_hints(user: User = Depends(require_manager)):
    """Return auto-detected local subnets to suggest in the scan UI."""
    return {"subnets": _detect_local_subnets()}


@router.get("/stats")
def system_stats(db: Session = Depends(get_db), user: User = Depends(get_current_user)):
    return {
        "categories": db.query(Category).count(),
        "products": db.query(Product).filter(Product.lifecycle_status == "active").count(),
        "tables": db.query(Table).filter(Table.is_active == True).count(),
        "table_groups": db.query(TableGroup).count(),
        "managers": db.query(User).filter(User.perm_access_dashboard == True, User.is_active == True).count(),
        "waiters": db.query(User).filter(User.perm_access_waiter_app == True, User.is_active == True).count(),
    }


@router.post("/lock")
def lock_system(token: str, user: User = Depends(require_sysadmin)):
    license_state["locked"] = True
    return {"status": "locked"}


@router.post("/unlock")
def unlock_system(token: str, user: User = Depends(require_sysadmin)):
    license_state["locked"] = False
    return {"status": "unlocked"}
