"""Tests for device CRUD routes.

These tests exercise the FastAPI route handlers using dependency override
to inject test stores, avoiding real hardware dependencies.
"""

from unittest.mock import AsyncMock, MagicMock

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from wled_controller.api.routes.devices import router
from wled_controller.storage.device_store import Device, DeviceStore
from wled_controller.storage.output_target_store import OutputTargetStore
from wled_controller.core.processing.processor_manager import ProcessorManager
from wled_controller.api import dependencies as deps


# ---------------------------------------------------------------------------
# App + fixtures (isolated from the real main app)
# ---------------------------------------------------------------------------


def _make_app():
    """Build a minimal FastAPI app with just the devices router + overrides."""
    app = FastAPI()
    app.include_router(router)
    return app


@pytest.fixture
def _route_db(tmp_path):
    from wled_controller.storage.database import Database
    db = Database(tmp_path / "test.db")
    yield db
    db.close()


@pytest.fixture
def device_store(_route_db):
    return DeviceStore(_route_db)


@pytest.fixture
def output_target_store(_route_db):
    return OutputTargetStore(_route_db)


@pytest.fixture
def processor_manager():
    """A mock ProcessorManager — avoids real hardware."""
    m = MagicMock(spec=ProcessorManager)
    m.add_device = MagicMock()
    m.remove_device = AsyncMock()
    m.update_device_info = MagicMock()
    m.find_device_state = MagicMock(return_value=None)
    m.get_all_device_health_dicts = MagicMock(return_value=[])
    return m


@pytest.fixture
def client(device_store, output_target_store, processor_manager):
    app = _make_app()

    # Override auth to always pass
    from wled_controller.api.auth import verify_api_key
    app.dependency_overrides[verify_api_key] = lambda: "test-user"

    # Override stores and manager
    app.dependency_overrides[deps.get_device_store] = lambda: device_store
    app.dependency_overrides[deps.get_output_target_store] = lambda: output_target_store
    app.dependency_overrides[deps.get_processor_manager] = lambda: processor_manager

    return TestClient(app, raise_server_exceptions=False)


# ---------------------------------------------------------------------------
# Helper to pre-populate a device
# ---------------------------------------------------------------------------


def _seed_device(store: DeviceStore, name="Test Device", led_count=100) -> Device:
    return store.create_device(
        name=name,
        url="http://192.168.1.100",
        led_count=led_count,
    )


# ---------------------------------------------------------------------------
# LIST
# ---------------------------------------------------------------------------


class TestListDevices:
    def test_list_empty(self, client):
        resp = client.get("/api/v1/devices")
        assert resp.status_code == 200
        data = resp.json()
        assert data["count"] == 0
        assert data["devices"] == []

    def test_list_with_devices(self, client, device_store):
        _seed_device(device_store, "Dev A")
        _seed_device(device_store, "Dev B")
        resp = client.get("/api/v1/devices")
        assert resp.status_code == 200
        data = resp.json()
        assert data["count"] == 2


# ---------------------------------------------------------------------------
# GET by ID
# ---------------------------------------------------------------------------


class TestGetDevice:
    def test_get_existing(self, client, device_store):
        d = _seed_device(device_store)
        resp = client.get(f"/api/v1/devices/{d.id}")
        assert resp.status_code == 200
        data = resp.json()
        assert data["id"] == d.id
        assert data["name"] == "Test Device"

    def test_get_not_found(self, client):
        resp = client.get("/api/v1/devices/nonexistent")
        assert resp.status_code == 404


# ---------------------------------------------------------------------------
# UPDATE
# ---------------------------------------------------------------------------


class TestUpdateDevice:
    def test_update_name(self, client, device_store):
        d = _seed_device(device_store)
        resp = client.put(
            f"/api/v1/devices/{d.id}",
            json={"name": "Renamed"},
        )
        assert resp.status_code == 200
        assert resp.json()["name"] == "Renamed"

    def test_update_led_count(self, client, device_store):
        d = _seed_device(device_store, led_count=100)
        resp = client.put(
            f"/api/v1/devices/{d.id}",
            json={"led_count": 300},
        )
        assert resp.status_code == 200
        assert resp.json()["led_count"] == 300

    def test_update_not_found(self, client):
        resp = client.put(
            "/api/v1/devices/missing_id",
            json={"name": "X"},
        )
        assert resp.status_code == 404


# ---------------------------------------------------------------------------
# DELETE
# ---------------------------------------------------------------------------


class TestDeleteDevice:
    def test_delete_existing(self, client, device_store):
        d = _seed_device(device_store)
        resp = client.delete(f"/api/v1/devices/{d.id}")
        assert resp.status_code == 204
        assert device_store.count() == 0

    def test_delete_not_found(self, client):
        resp = client.delete("/api/v1/devices/missing_id")
        assert resp.status_code == 404

    def test_delete_referenced_by_target_returns_409(
        self, client, device_store, output_target_store
    ):
        d = _seed_device(device_store)
        output_target_store.create_target(
            name="Target",
            target_type="led",
            device_id=d.id,
        )
        resp = client.delete(f"/api/v1/devices/{d.id}")
        assert resp.status_code == 409
        assert "referenced" in resp.json()["detail"].lower()


# ---------------------------------------------------------------------------
# Batch states
# ---------------------------------------------------------------------------


class TestBatchStates:
    def test_batch_states(self, client):
        resp = client.get("/api/v1/devices/batch/states")
        assert resp.status_code == 200
        assert "states" in resp.json()
