"""LogShipper tests — redaction, bounded queue, fail-open, real-server ship."""

from __future__ import annotations

import http.server
import json
import logging
import threading
import time
from typing import ClassVar

import pytest
from mgf.common._identity import _set_identity

from mgf.logship import LogShipper
from mgf.logship._protocol import INGEST_PATH


@pytest.fixture(autouse=True)
def _identity() -> None:
    # _to_wire reads app_name()/app_version(); set identity so they don't fire
    # UnconfiguredWarning (the suite escalates warnings to errors).
    _set_identity("ship-test", "1.0.0")


def _rec(**over: object) -> logging.LogRecord:
    base: dict[str, object] = {"name": "app", "levelname": "INFO", "levelno": 20, "msg": "hi"}
    base.update(over)
    return logging.makeLogRecord(base)


class TestSerialization:
    def test_redacts_before_queue(self) -> None:
        shipper = LogShipper("http://localhost:0", app="ship-test")
        wire = shipper._to_wire(_rec(name="app.auth", msg="login", password="hunter2"))
        assert wire["logger"] == "app.auth"
        assert "hunter2" not in json.dumps(wire)  # redacted
        assert wire["app"] == "ship-test"


class TestBounded:
    def test_overflow_drops_oldest_and_counts(self) -> None:
        shipper = LogShipper("http://localhost:0", app="ship-test", max_queue=3)
        # Don't start the worker; just exercise the queue.
        shipper._started = True
        for i in range(5):
            shipper.emit(_rec(msg=f"m{i}"))
        assert shipper.dropped_count == 2
        assert len(shipper._q) == 3


class TestFailOpen:
    def test_unreachable_collector_never_raises(self) -> None:
        # Port 1 is unreachable; flushing must swallow the error.
        shipper = LogShipper("http://127.0.0.1:1", app="ship-test", timeout=0.5)
        shipper._started = True
        shipper.emit(_rec(msg="will fail to ship"))
        shipper._drain_and_post()  # synchronous flush, must not raise
        assert shipper.failed_count == 1
        assert shipper.shipped_count == 0


class _CaptureHandler(http.server.BaseHTTPRequestHandler):
    received: ClassVar[list[tuple[str, bytes, dict[str, str]]]] = []

    def do_POST(self) -> None:
        n = int(self.headers.get("Content-Length", 0))
        body = self.rfile.read(n)
        type(self).received.append((self.path, body, dict(self.headers)))
        self.send_response(204)
        self.end_headers()

    def log_message(self, *args: object) -> None:
        pass


class TestRealShip:
    def test_ships_ndjson_to_a_live_server(self) -> None:
        _CaptureHandler.received = []
        srv = http.server.HTTPServer(("127.0.0.1", 0), _CaptureHandler)
        port = srv.server_address[1]
        threading.Thread(target=srv.serve_forever, daemon=True).start()
        try:
            shipper = LogShipper(
                f"http://127.0.0.1:{port}",
                app="ship-test",
                token="sek",
                flush_interval=0.05,
            )
            shipper.emit(_rec(name="app.web", msg="served"))
            deadline = time.monotonic() + 3
            while not _CaptureHandler.received and time.monotonic() < deadline:
                time.sleep(0.02)
            shipper.close()
        finally:
            srv.shutdown()
            srv.server_close()

        assert _CaptureHandler.received, "collector received nothing"
        path, body, headers = _CaptureHandler.received[0]
        assert path == INGEST_PATH
        assert headers.get("Authorization") == "Bearer sek"
        assert headers.get("X-Logship-Protocol")
        rec = json.loads(body.decode().splitlines()[0])
        assert rec["logger"] == "app.web"
        assert rec["msg"] == "served"
        assert shipper.shipped_count >= 1
