"""Tests for ServiceDataHarness lifecycle and QbittorrentSampleStore operations."""

from __future__ import annotations

import pytest

from media_library_viewer_api.services.qbittorrent_store import (
    MAX_SAMPLES,
    QBITTORRENT_CONCERN,
    QbittorrentSampleStore,
)
from media_library_viewer_api.services.service_data import (
    ServiceDataHarness,
    StorageConcern,
)

# A throwaway concern used to test harness lifecycle in isolation.
_TEST_CONCERN = StorageConcern(
    concern_key="test",
    db_filename="test.db",
    migrations=[
        """
        CREATE TABLE IF NOT EXISTS test_items (
            id INTEGER PRIMARY KEY,
            service_id TEXT NOT NULL,
            value TEXT
        );
        CREATE INDEX IF NOT EXISTS idx_test_service ON test_items(service_id);
        """
    ],
    tables=["test_items"],
)


class TestServiceDataHarnessMigrations:
    def test_run_migrations_creates_tables(self, tmp_path):
        harness = ServiceDataHarness(tmp_path)
        harness.register(_TEST_CONCERN)
        harness.run_migrations()

        with harness.connect("test") as conn:
            tables = {row[0] for row in conn.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()}
        assert "test_items" in tables

    def test_migrations_are_idempotent(self, tmp_path):
        """Re-running migrations on an already-migrated DB must not crash."""
        harness = ServiceDataHarness(tmp_path)
        harness.register(_TEST_CONCERN)
        harness.run_migrations()
        harness.run_migrations()  # should not raise

    def test_alter_table_idempotency(self, tmp_path):
        """ALTER TABLE ADD COLUMN must be silently skipped on re-run."""
        concern = StorageConcern(
            concern_key="alter_test",
            db_filename="alter.db",
            migrations=[
                "CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY)",
                "ALTER TABLE items ADD COLUMN extra TEXT DEFAULT ''",
            ],
            tables=["items"],
        )
        harness = ServiceDataHarness(tmp_path)
        harness.register(concern)
        harness.run_migrations()
        harness.run_migrations()  # second run: "duplicate column name" caught

        with harness.connect("alter_test") as conn:
            cols = {row[1] for row in conn.execute("PRAGMA table_info(items)").fetchall()}
        assert "extra" in cols


class TestServiceDataHarnessCascadeDelete:
    def test_cascade_delete_removes_only_matching_service(self, tmp_path):
        harness = ServiceDataHarness(tmp_path)
        harness.register(_TEST_CONCERN)
        harness.run_migrations()

        with harness.connect("test") as conn:
            conn.execute("INSERT INTO test_items (id, service_id, value) VALUES (1, 'svc-a', 'a1')")
            conn.execute("INSERT INTO test_items (id, service_id, value) VALUES (2, 'svc-a', 'a2')")
            conn.execute("INSERT INTO test_items (id, service_id, value) VALUES (3, 'svc-b', 'b1')")
            conn.commit()

        harness.cascade_delete("svc-a")

        with harness.connect("test") as conn:
            remaining = conn.execute("SELECT service_id, value FROM test_items ORDER BY id").fetchall()
        assert len(remaining) == 1
        assert remaining[0][0] == "svc-b"

    def test_cascade_delete_skips_missing_concern_db(self, tmp_path):
        """cascade_delete on a concern whose DB file doesn't exist should not crash."""
        harness = ServiceDataHarness(tmp_path)
        harness.register(_TEST_CONCERN)
        # No run_migrations → DB file doesn't exist
        harness.cascade_delete("svc-x")  # should not raise


class TestQbittorrentSampleStore:
    @pytest.fixture()
    def store(self, tmp_path):
        harness = ServiceDataHarness(tmp_path)
        harness.register(QBITTORRENT_CONCERN)
        harness.run_migrations()
        return QbittorrentSampleStore(harness=harness)

    def test_append_and_window(self, store):
        store.append("svc-1", ts=100, dl_speed=500, up_speed=50)
        store.append("svc-1", ts=200, dl_speed=600, up_speed=60)
        store.append("svc-1", ts=300, dl_speed=700, up_speed=70)

        samples = store.window("svc-1")
        assert len(samples) == 3
        assert samples[0]["ts"] == 100
        assert samples[2]["ts"] == 300
        assert samples[1]["dl_speed"] == 600

    def test_window_with_since_ts(self, store):
        store.append("svc-1", ts=100, dl_speed=500, up_speed=50)
        store.append("svc-1", ts=200, dl_speed=600, up_speed=60)
        store.append("svc-1", ts=300, dl_speed=700, up_speed=70)

        samples = store.window("svc-1", since_ts=200)
        assert len(samples) == 2
        assert samples[0]["ts"] == 200

    def test_prune_enforces_max_samples(self, store):
        for i in range(MAX_SAMPLES + 10):
            store.append("svc-1", ts=i, dl_speed=i, up_speed=i)

        samples = store.window("svc-1")
        assert len(samples) == MAX_SAMPLES
        # The oldest 10 should have been pruned
        assert samples[0]["ts"] == 10
        assert samples[-1]["ts"] == MAX_SAMPLES + 9

    def test_two_services_do_not_cross_contaminate(self, store):
        store.append("svc-a", ts=100, dl_speed=500, up_speed=50)
        store.append("svc-b", ts=200, dl_speed=600, up_speed=60)

        a_samples = store.window("svc-a")
        b_samples = store.window("svc-b")

        assert len(a_samples) == 1
        assert a_samples[0]["dl_speed"] == 500
        assert len(b_samples) == 1
        assert b_samples[0]["dl_speed"] == 600
