"""Post-remediation consumer hardening tests."""

from __future__ import annotations

import threading

from neonlink.circuit_breaker import CircuitBreaker, CircuitBreakerState
from neonlink.config import Config
from neonlink.consumer import Consumer


class _FakeMessage:
    def __init__(
        self,
        topic: str = "topic-a",
        partition: int = 0,
        offset: int = 1,
        err=None,
    ) -> None:
        self._topic = topic
        self._partition = partition
        self._offset = offset
        self._err = err

    def error(self):
        return self._err

    def topic(self):
        return self._topic

    def partition(self):
        return self._partition

    def offset(self):
        return self._offset

    def headers(self):
        return []

    def key(self):
        return b"k"

    def value(self):
        return b"v"

    def timestamp(self):
        return (0, 0)


class _FakePollError:
    def __init__(self, code: int) -> None:
        self._code = code

    def code(self):
        return self._code


class _FakeCommitConsumer:
    def __init__(self) -> None:
        self.commit_calls = 0

    def commit(self, message=None, asynchronous=False):
        self.commit_calls += 1


def test_commit_fenced_for_revoked_partition():
    c = object.__new__(Consumer)
    c._revoked_partitions = {"topic-a:1"}
    c._revoked_lock = threading.Lock()
    c._consumer = _FakeCommitConsumer()

    ok = Consumer._commit(c, _FakeMessage(topic="topic-a", partition=1, offset=44))
    assert ok is False
    assert c._consumer.commit_calls == 0


def test_subscribe_with_dlq_forwards_cancel_event():
    c = object.__new__(Consumer)
    called = {}

    def _fake_subscribe(handler, *, cancel_event=None):
        called["handler"] = handler
        called["cancel_event"] = cancel_event

    c.subscribe = _fake_subscribe

    handler = object()
    dlq = object()
    ev = threading.Event()
    Consumer.subscribe_with_dlq(c, handler, dlq, cancel_event=ev)

    assert c._dlq_producer is dlq
    assert called["handler"] is handler
    assert called["cancel_event"] is ev


def test_poll_message_error_trips_circuit_breaker(monkeypatch):
    import neonlink.consumer as consumer_mod

    class _DummyKafkaException(Exception):
        pass

    class _DummyKafkaError:
        _PARTITION_EOF = -191

    class _FakePollConsumer:
        def poll(self, timeout=1.0):
            return _FakeMessage(err=_FakePollError(code=123))

    monkeypatch.setattr(consumer_mod, "KafkaException", _DummyKafkaException)
    monkeypatch.setattr(consumer_mod, "KafkaError", _DummyKafkaError)

    c = object.__new__(Consumer)
    c._consumer = _FakePollConsumer()
    c._cb = CircuitBreaker("poll", max_failures=1, reset_timeout_sec=60)
    c._last_poll_error = False

    msg = Consumer._poll_one(c)
    assert msg is None
    assert c._last_poll_error is True
    assert c._cb.state == CircuitBreakerState.OPEN


def test_handler_with_commit_controls_ack(monkeypatch):
    import neonlink.consumer as consumer_mod

    class _FakeConfluentConsumer:
        def __init__(self, _cfg):
            self.commit_calls = 0
            self.poll_calls = 0

        def subscribe(self, _topics, on_revoke=None, on_assign=None):
            self._on_revoke = on_revoke
            self._on_assign = on_assign

        def poll(self, timeout=1.0):
            if self.poll_calls == 0:
                self.poll_calls += 1
                return _FakeMessage(topic="topic-a", partition=0, offset=10)
            return None

        def commit(self, message=None, asynchronous=False):
            self.commit_calls += 1

        def assignment(self):
            return []

        def pause(self, _parts):
            return None

        def resume(self, _parts):
            return None

        def get_watermark_offsets(self, _tp, cached=True):
            return (0, 11)

        def close(self):
            return None

    monkeypatch.setattr(consumer_mod, "ConfluentConsumer", _FakeConfluentConsumer)

    cfg = Config(consumer_group="group-a")
    c = Consumer(cfg, topics=["topic-a"])
    stop_event = threading.Event()

    class _Handler:
        def __init__(self):
            self.called_manual = False
            self.called_auto = False

        def handle_message(self, _record):
            self.called_auto = True

        def handle_message_with_commit(self, _record, commit):
            self.called_manual = True
            assert commit() is True
            stop_event.set()

    h = _Handler()
    c.subscribe(h, cancel_event=stop_event)

    assert h.called_manual is True
    assert h.called_auto is False
    assert c._consumer.commit_calls == 1
