"""Tests for the background worker."""

import json
import threading
import time

import pytest
import httpx

from genosis.buffer import DiskBuffer
from genosis.worker import BackgroundWorker


class FakeServer:
    """Minimal HTTP server that records received requests."""

    def __init__(self):
        self.received: list[dict] = []
        self.should_fail = False
        self._server = None
        self.port = 0

    def start(self, port: int = 0):
        from http.server import HTTPServer, BaseHTTPRequestHandler

        received = self.received
        should_fail = self

        class Handler(BaseHTTPRequestHandler):
            def do_POST(self):
                length = int(self.headers.get("Content-Length", 0))
                body = json.loads(self.rfile.read(length)) if length else {}
                received.append({"path": self.path, "body": body})

                if should_fail.should_fail:
                    self.send_response(500)
                    self.end_headers()
                    self.wfile.write(b'{"error":"fail"}')
                else:
                    self.send_response(200)
                    self.end_headers()
                    self.wfile.write(b'{"ok":true}')

            def log_message(self, format, *args):
                pass  # Suppress logs

        self._server = HTTPServer(("127.0.0.1", port), Handler)
        self.port = self._server.server_address[1]
        self._thread = threading.Thread(target=self._server.serve_forever, daemon=True)
        self._thread.start()
        return self

    def stop(self):
        if self._server:
            self._server.shutdown()


@pytest.fixture
def fake_server():
    server = FakeServer().start()
    yield server
    server.stop()


@pytest.fixture
def buf(tmp_path):
    db_path = str(tmp_path / "worker_test.db")
    return DiskBuffer(path=db_path)


class TestBackgroundWorker:
    def test_drains_telemetry_events(self, fake_server, buf):
        buf.put("telemetry", {"event_id": "e1", "model": "test"})
        buf.put("telemetry", {"event_id": "e2", "model": "test"})

        worker = BackgroundWorker(
            buffer=buf,
            base_url=f"http://127.0.0.1:{fake_server.port}",
            api_key="test_key",
            drain_interval=0.1,
        )
        worker.start()
        time.sleep(1.0)  # Let worker drain
        worker.shutdown(timeout=2.0)

        # Events should be drained from buffer
        assert buf.size() == 0
        # Server should have received a batch POST
        assert len(fake_server.received) >= 1
        ingest_calls = [r for r in fake_server.received if r["path"] == "/v1/ingest"]
        assert len(ingest_calls) >= 1

    def test_drains_manifest_ack(self, fake_server, buf):
        buf.put("manifest_ack", {"event_id": "m1", "manifest_version": "v1"})

        worker = BackgroundWorker(
            buffer=buf,
            base_url=f"http://127.0.0.1:{fake_server.port}",
            api_key="test_key",
            drain_interval=0.1,
        )
        worker.start()
        time.sleep(1.0)
        worker.shutdown(timeout=2.0)

        assert buf.size() == 0
        ack_calls = [r for r in fake_server.received if r["path"] == "/v1/manifest/ack"]
        assert len(ack_calls) == 1

    def test_retries_on_failure(self, fake_server, buf):
        fake_server.should_fail = True
        buf.put("telemetry", {"event_id": "e1"})

        worker = BackgroundWorker(
            buffer=buf,
            base_url=f"http://127.0.0.1:{fake_server.port}",
            api_key="test_key",
            drain_interval=0.1,
        )
        worker.start()
        time.sleep(1.0)

        # Buffer should still have the event (not confirmed)
        assert buf.size() == 1

        # Now make server succeed
        fake_server.should_fail = False
        time.sleep(2.0)

        # Should be drained now
        assert buf.size() == 0
        worker.shutdown(timeout=2.0)

    def test_does_not_remove_on_failure(self, fake_server, buf):
        fake_server.should_fail = True
        buf.put("telemetry", {"event_id": "e1"})
        buf.put("telemetry", {"event_id": "e2"})

        worker = BackgroundWorker(
            buffer=buf,
            base_url=f"http://127.0.0.1:{fake_server.port}",
            api_key="test_key",
            drain_interval=0.1,
        )
        worker.start()
        time.sleep(1.0)
        worker.shutdown(timeout=2.0)

        # Both events should still be in buffer
        assert buf.size() == 2

    def test_shutdown_attempts_final_flush(self, fake_server, buf):
        buf.put("telemetry", {"event_id": "e1"})

        worker = BackgroundWorker(
            buffer=buf,
            base_url=f"http://127.0.0.1:{fake_server.port}",
            api_key="test_key",
            drain_interval=60.0,  # Long interval — won't drain naturally
        )
        worker.start()
        time.sleep(0.2)  # Let thread start but not drain yet

        # Shutdown should flush
        worker.shutdown(timeout=5.0)
        assert buf.size() == 0
