"""
Linux BenchTools - Benchmark API
"""

import json
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
from datetime import datetime

from app.db.session import get_db
from app.core.security import verify_token
from app.schemas.benchmark import (
    BenchmarkPayload,
    BenchmarkResponse,
    BenchmarkDetail,
    BenchmarkSummary,
    BenchmarkUpdate,
)
from app.models.device import Device
from app.models.hardware_snapshot import HardwareSnapshot
from app.models.benchmark import Benchmark
from app.utils.scoring import (
    calculate_global_score,
    calculate_cpu_score,
    calculate_memory_score,
    calculate_disk_score,
    calculate_network_score,
    calculate_gpu_score
)

router = APIRouter()


@router.post("/benchmark", response_model=BenchmarkResponse, status_code=status.HTTP_200_OK)
async def submit_benchmark(
    payload: BenchmarkPayload,
    db: Session = Depends(get_db),
    _: bool = Depends(verify_token)
):
    """
    Submit a benchmark result from a client machine.

    This endpoint:
    1. Resolves or creates the device
    2. Creates a hardware snapshot
    3. Creates a benchmark record
    4. Returns device_id and benchmark_id
    """

    # 1. Resolve or create device
    device = db.query(Device).filter(Device.hostname == payload.device_identifier).first()

    if not device:
        device = Device(
            hostname=payload.device_identifier,
            created_at=datetime.utcnow(),
            updated_at=datetime.utcnow()
        )
        db.add(device)
        db.flush()  # Get device.id

    # Update device timestamp
    device.updated_at = datetime.utcnow()

    # 2. Get or create hardware snapshot
    hw = payload.hardware

    # Check if we have an existing snapshot for this device
    existing_snapshot = db.query(HardwareSnapshot).filter(
        HardwareSnapshot.device_id == device.id
    ).order_by(HardwareSnapshot.captured_at.desc()).first()

    # If we have an existing snapshot, update it instead of creating a new one
    if existing_snapshot:
        snapshot = existing_snapshot
        snapshot.captured_at = datetime.utcnow()  # Update timestamp
    else:
        # Create new snapshot if none exists
        snapshot = HardwareSnapshot(
            device_id=device.id,
            captured_at=datetime.utcnow()
        )

    # Update all fields (whether new or existing snapshot)
    # CPU
    snapshot.cpu_vendor = hw.cpu.vendor if hw.cpu else None
    snapshot.cpu_model = hw.cpu.model if hw.cpu else None
    snapshot.cpu_microarchitecture = hw.cpu.microarchitecture if hw.cpu else None
    snapshot.cpu_cores = hw.cpu.cores if hw.cpu else None
    snapshot.cpu_threads = hw.cpu.threads if hw.cpu else None
    snapshot.cpu_base_freq_ghz = hw.cpu.base_freq_ghz if hw.cpu else None
    snapshot.cpu_max_freq_ghz = hw.cpu.max_freq_ghz if hw.cpu else None
    snapshot.cpu_cache_l1_kb = hw.cpu.cache_l1_kb if hw.cpu else None
    snapshot.cpu_cache_l2_kb = hw.cpu.cache_l2_kb if hw.cpu else None
    snapshot.cpu_cache_l3_kb = hw.cpu.cache_l3_kb if hw.cpu else None
    snapshot.cpu_flags = json.dumps(hw.cpu.flags) if hw.cpu and hw.cpu.flags else None
    snapshot.cpu_tdp_w = hw.cpu.tdp_w if hw.cpu else None

    # RAM
    snapshot.ram_total_mb = hw.ram.total_mb if hw.ram else None
    snapshot.ram_used_mb = hw.ram.used_mb if hw.ram else None
    snapshot.ram_free_mb = hw.ram.free_mb if hw.ram else None
    snapshot.ram_shared_mb = hw.ram.shared_mb if hw.ram else None
    snapshot.ram_slots_total = hw.ram.slots_total if hw.ram else None
    snapshot.ram_slots_used = hw.ram.slots_used if hw.ram else None
    snapshot.ram_ecc = hw.ram.ecc if hw.ram else None
    snapshot.ram_layout_json = json.dumps([slot.model_dump() for slot in hw.ram.layout]) if hw.ram and hw.ram.layout else None

    # GPU
    snapshot.gpu_summary = f"{hw.gpu.vendor} {hw.gpu.model}" if hw.gpu and hw.gpu.model else None
    snapshot.gpu_vendor = hw.gpu.vendor if hw.gpu else None
    snapshot.gpu_model = hw.gpu.model if hw.gpu else None
    snapshot.gpu_driver_version = hw.gpu.driver_version if hw.gpu else None
    snapshot.gpu_memory_dedicated_mb = hw.gpu.memory_dedicated_mb if hw.gpu else None
    snapshot.gpu_memory_shared_mb = hw.gpu.memory_shared_mb if hw.gpu else None
    snapshot.gpu_api_support = json.dumps(hw.gpu.api_support) if hw.gpu and hw.gpu.api_support else None

    # Storage
    snapshot.storage_summary = f"{len(hw.storage.devices)} device(s)" if hw.storage and hw.storage.devices else None
    snapshot.storage_devices_json = json.dumps([d.model_dump() for d in hw.storage.devices]) if hw.storage and hw.storage.devices else None
    snapshot.partitions_json = json.dumps([p.model_dump() for p in hw.storage.partitions]) if hw.storage and hw.storage.partitions else None

    # Network
    snapshot.network_interfaces_json = json.dumps([i.model_dump() for i in hw.network.interfaces]) if hw.network and hw.network.interfaces else None
    snapshot.network_shares_json = json.dumps([share.model_dump() for share in hw.network_shares]) if hw.network_shares else None

    # OS / Motherboard
    snapshot.os_name = hw.os.name if hw.os else None
    snapshot.os_version = hw.os.version if hw.os else None
    snapshot.kernel_version = hw.os.kernel_version if hw.os else None
    snapshot.architecture = hw.os.architecture if hw.os else None
    snapshot.virtualization_type = hw.os.virtualization_type if hw.os else None
    snapshot.screen_resolution = hw.os.screen_resolution if hw.os else None
    snapshot.display_server = hw.os.display_server if hw.os else None
    snapshot.session_type = hw.os.session_type if hw.os else None
    snapshot.last_boot_time = hw.os.last_boot_time if hw.os else None
    snapshot.uptime_seconds = hw.os.uptime_seconds if hw.os else None
    snapshot.battery_percentage = hw.os.battery_percentage if hw.os else None
    snapshot.battery_status = hw.os.battery_status if hw.os else None
    snapshot.battery_health = hw.os.battery_health if hw.os else None
    snapshot.hostname = hw.os.hostname if hw.os else None
    snapshot.desktop_environment = hw.os.desktop_environment if hw.os else None
    snapshot.motherboard_vendor = hw.motherboard.vendor if hw.motherboard else None
    snapshot.motherboard_model = hw.motherboard.model if hw.motherboard else None
    snapshot.bios_vendor = hw.motherboard.bios_vendor if hw.motherboard and hasattr(hw.motherboard, 'bios_vendor') else None
    snapshot.bios_version = hw.motherboard.bios_version if hw.motherboard else None
    snapshot.bios_date = hw.motherboard.bios_date if hw.motherboard else None

    # PCI and USB Devices
    snapshot.pci_devices_json = json.dumps([d.model_dump(by_alias=True) for d in hw.pci_devices]) if hw.pci_devices else None
    snapshot.usb_devices_json = json.dumps([d.model_dump() for d in hw.usb_devices]) if hw.usb_devices else None

    # Misc
    snapshot.sensors_json = json.dumps(hw.sensors.model_dump()) if hw.sensors else None
    snapshot.raw_info_json = json.dumps(hw.raw_info.model_dump()) if hw.raw_info else None

    # Add to session only if it's a new snapshot
    if not existing_snapshot:
        db.add(snapshot)

    db.flush()  # Get snapshot.id for new snapshots

    # 3. Create benchmark
    results = payload.results

    # Recalculate scores from raw metrics using new formulas
    cpu_score = None
    cpu_score_single = None
    cpu_score_multi = None

    if results.cpu:
        # Use scores from script if available (preferred), otherwise calculate
        if results.cpu.score_single is not None:
            cpu_score_single = results.cpu.score_single
        elif results.cpu.events_per_sec_single:
            cpu_score_single = calculate_cpu_score(results.cpu.events_per_sec_single)

        if results.cpu.score_multi is not None:
            cpu_score_multi = results.cpu.score_multi
        elif results.cpu.events_per_sec_multi:
            cpu_score_multi = calculate_cpu_score(results.cpu.events_per_sec_multi)

        # Use score from script if available, otherwise calculate
        if results.cpu.score is not None:
            cpu_score = results.cpu.score
        elif results.cpu.events_per_sec_multi:
            cpu_score = cpu_score_multi
        elif results.cpu.events_per_sec:
            cpu_score = calculate_cpu_score(results.cpu.events_per_sec)

    memory_score = None
    if results.memory and results.memory.throughput_mib_s:
        memory_score = calculate_memory_score(results.memory.throughput_mib_s)

    disk_score = None
    if results.disk:
        disk_score = calculate_disk_score(
            read_mb_s=results.disk.read_mb_s,
            write_mb_s=results.disk.write_mb_s
        )

    network_score = None
    if results.network:
        network_score = calculate_network_score(
            upload_mbps=results.network.upload_mbps,
            download_mbps=results.network.download_mbps
        )

    gpu_score = None
    if results.gpu and results.gpu.glmark2_score:
        gpu_score = calculate_gpu_score(results.gpu.glmark2_score)

    # Calculate global score from recalculated component scores
    global_score = calculate_global_score(
        cpu_score=cpu_score,
        memory_score=memory_score,
        disk_score=disk_score,
        network_score=network_score,
        gpu_score=gpu_score
    )

    # Extract network results for easier frontend access
    network_results = None
    if results.network:
        network_results = {
            "upload_mbps": results.network.upload_mbps if hasattr(results.network, 'upload_mbps') else None,
            "download_mbps": results.network.download_mbps if hasattr(results.network, 'download_mbps') else None,
            "ping_ms": results.network.ping_ms if hasattr(results.network, 'ping_ms') else None,
            "score": network_score
        }

    benchmark = Benchmark(
        device_id=device.id,
        hardware_snapshot_id=snapshot.id,
        run_at=datetime.utcnow(),
        bench_script_version=payload.bench_script_version,

        global_score=global_score,
        cpu_score=cpu_score,
        cpu_score_single=cpu_score_single,
        cpu_score_multi=cpu_score_multi,
        memory_score=memory_score,
        disk_score=disk_score,
        network_score=network_score,
        gpu_score=gpu_score,

        details_json=json.dumps(results.dict()),
        network_results_json=json.dumps(network_results) if network_results else None
    )

    db.add(benchmark)
    db.commit()

    return BenchmarkResponse(
        status="ok",
        device_id=device.id,
        benchmark_id=benchmark.id,
        message=f"Benchmark successfully recorded for device '{device.hostname}'"
    )


@router.get("/benchmarks/{benchmark_id}", response_model=BenchmarkDetail)
async def get_benchmark(
    benchmark_id: int,
    db: Session = Depends(get_db)
):
    """
    Get detailed benchmark information
    """
    benchmark = db.query(Benchmark).filter(Benchmark.id == benchmark_id).first()

    if not benchmark:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Benchmark {benchmark_id} not found"
        )

    return BenchmarkDetail(
        id=benchmark.id,
        device_id=benchmark.device_id,
        hardware_snapshot_id=benchmark.hardware_snapshot_id,
        run_at=benchmark.run_at.isoformat(),
        bench_script_version=benchmark.bench_script_version,
        global_score=benchmark.global_score,
        cpu_score=benchmark.cpu_score,
        cpu_score_single=benchmark.cpu_score_single,
        cpu_score_multi=benchmark.cpu_score_multi,
        memory_score=benchmark.memory_score,
        disk_score=benchmark.disk_score,
        network_score=benchmark.network_score,
        gpu_score=benchmark.gpu_score,
        details=json.loads(benchmark.details_json),
        notes=benchmark.notes
    )


@router.patch("/benchmarks/{benchmark_id}", response_model=BenchmarkSummary)
async def update_benchmark_entry(
    benchmark_id: int,
    payload: BenchmarkUpdate,
    db: Session = Depends(get_db)
):
    """
    Update editable benchmark fields (currently only notes).
    """
    benchmark = db.query(Benchmark).filter(Benchmark.id == benchmark_id).first()

    if not benchmark:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Benchmark {benchmark_id} not found"
        )

    update_data = payload.model_dump(exclude_unset=True)

    if "notes" in update_data:
        benchmark.notes = update_data["notes"]

    db.add(benchmark)
    db.commit()
    db.refresh(benchmark)

    return BenchmarkSummary(
        id=benchmark.id,
        run_at=benchmark.run_at.isoformat(),
        global_score=benchmark.global_score,
        cpu_score=benchmark.cpu_score,
        cpu_score_single=benchmark.cpu_score_single,
        cpu_score_multi=benchmark.cpu_score_multi,
        memory_score=benchmark.memory_score,
        disk_score=benchmark.disk_score,
        network_score=benchmark.network_score,
        gpu_score=benchmark.gpu_score,
        bench_script_version=benchmark.bench_script_version,
        notes=benchmark.notes
    )
