"""Tests for neonlink.config."""

import os
import pytest

from neonlink.config import Config, new_config_from_env


class TestDefaultConfig:
    def test_defaults(self):
        cfg = Config()
        assert cfg.brokers == ["localhost:9092"]
        assert cfg.max_poll_records == 10
        assert cfg.session_timeout_sec == 30
        assert cfg.auto_offset_reset == "earliest"
        assert cfg.required_acks == "all"
        assert cfg.transactional_id == ""
        assert cfg.cb_max_failures == 5
        assert cfg.retry_max_attempts == 3
        assert cfg.retry_initial_wait_ms == 500
        assert cfg.retry_max_wait_ms == 30000
        assert cfg.dlq_topic == "errors-dlq"
        assert cfg.poison_pill_threshold == 5


class TestNewConfigFromEnv:
    def test_loads_from_env(self, monkeypatch):
        monkeypatch.setenv("NEONLINK_BROKERS", "broker1:9092,broker2:9092")
        monkeypatch.setenv("NEONLINK_TLS_ENABLED", "true")
        monkeypatch.setenv("NEONLINK_CONSUMER_GROUP", "test-group")
        monkeypatch.setenv("NEONLINK_MAX_POLL_RECORDS", "50")
        monkeypatch.setenv("NEONLINK_SESSION_TIMEOUT_SEC", "45")
        monkeypatch.setenv("NEONLINK_AUTO_OFFSET_RESET", "latest")
        monkeypatch.setenv("NEONLINK_REQUIRED_ACKS", "all")
        monkeypatch.setenv("NEONLINK_TRANSACTIONAL_ID", "txn-test-0")
        monkeypatch.setenv("NEONLINK_CB_MAX_FAILURES", "10")
        monkeypatch.setenv("NEONLINK_RETRY_MAX_ATTEMPTS", "5")
        monkeypatch.setenv("NEONLINK_RETRY_INITIAL_WAIT_MS", "1000")
        monkeypatch.setenv("NEONLINK_RETRY_MAX_WAIT_MS", "60000")
        monkeypatch.setenv("NEONLINK_DLQ_TOPIC", "custom-dlq")
        monkeypatch.setenv("NEONLINK_POISON_THRESHOLD", "3")
        monkeypatch.setenv("NEONLINK_CLIENT_ID", "test-client")

        cfg = new_config_from_env()

        assert cfg.brokers == ["broker1:9092", "broker2:9092"]
        assert cfg.tls_enabled is True
        assert cfg.consumer_group == "test-group"
        assert cfg.max_poll_records == 50
        assert cfg.session_timeout_sec == 45
        assert cfg.auto_offset_reset == "latest"
        assert cfg.transactional_id == "txn-test-0"
        assert cfg.cb_max_failures == 10
        assert cfg.retry_max_attempts == 5
        assert cfg.retry_initial_wait_ms == 1000
        assert cfg.retry_max_wait_ms == 60000
        assert cfg.dlq_topic == "custom-dlq"
        assert cfg.poison_pill_threshold == 3
        assert cfg.client_id == "test-client"

class TestConfigValidate:
    def test_valid_default(self):
        Config().validate()

    def test_no_brokers(self):
        with pytest.raises(ValueError, match="at least one broker"):
            Config(brokers=[]).validate()

    def test_empty_broker(self):
        with pytest.raises(ValueError, match="cannot be empty"):
            Config(brokers=[""]).validate()

    def test_tls_without_certs(self):
        with pytest.raises(ValueError, match="TLS enabled"):
            Config(tls_enabled=True).validate()

    def test_sasl_without_credentials(self):
        with pytest.raises(ValueError, match="username/password"):
            Config(sasl_mechanism="PLAIN").validate()

    def test_unsupported_sasl(self):
        with pytest.raises(ValueError, match="unsupported SASL"):
            Config(
                sasl_mechanism="KERBEROS",
                sasl_username="u",
                sasl_password="p",
            ).validate()

    def test_valid_sasl_plain(self):
        Config(
            sasl_mechanism="PLAIN",
            sasl_username="u",
            sasl_password="p",
        ).validate()

    def test_valid_sasl_scram256(self):
        Config(
            sasl_mechanism="SCRAM-SHA-256",
            sasl_username="u",
            sasl_password="p",
        ).validate()

    def test_poison_threshold_zero(self):
        with pytest.raises(ValueError, match="poison pill threshold"):
            Config(poison_pill_threshold=0).validate()


class TestConfluentConfig:
    def test_basic(self):
        cfg = Config(brokers=["b1:9092", "b2:9092"], client_id="test")
        conf = cfg.to_confluent_config()
        assert conf["bootstrap.servers"] == "b1:9092,b2:9092"
        assert conf["client.id"] == "test"

    def test_tls(self):
        cfg = Config(
            tls_enabled=True,
            tls_ca_file="/ca.pem",
            tls_cert_file="/cert.pem",
            tls_key_file="/key.pem",
        )
        conf = cfg.to_confluent_config()
        assert conf["security.protocol"] == "SSL"
        assert conf["ssl.ca.location"] == "/ca.pem"

    def test_sasl_plain(self):
        cfg = Config(
            sasl_mechanism="PLAIN",
            sasl_username="user",
            sasl_password="pass",
        )
        conf = cfg.to_confluent_config()
        assert conf["security.protocol"] == "SASL_PLAINTEXT"
        assert conf["sasl.mechanism"] == "PLAIN"
        assert conf["sasl.username"] == "user"

    def test_sasl_with_tls(self):
        cfg = Config(
            tls_enabled=True,
            tls_ca_file="/ca.pem",
            sasl_mechanism="SCRAM-SHA-512",
            sasl_username="user",
            sasl_password="pass",
        )
        conf = cfg.to_confluent_config()
        assert conf["security.protocol"] == "SASL_SSL"

    def test_producer_config(self):
        cfg = Config()
        conf = cfg.to_producer_config()
        assert conf["acks"] == "all"
        assert conf["retries"] == "3"
        assert conf["retry.backoff.ms"] == "500"
        assert conf["retry.backoff.max.ms"] == "30000"

    def test_producer_config_custom_retry(self):
        cfg = Config(retry_initial_wait_ms=1000, retry_max_wait_ms=60000)
        conf = cfg.to_producer_config()
        assert conf["retry.backoff.ms"] == "1000"
        assert conf["retry.backoff.max.ms"] == "60000"

    def test_consumer_config(self):
        cfg = Config(consumer_group="test-group")
        conf = cfg.to_consumer_config()
        assert conf["group.id"] == "test-group"
        assert conf["enable.auto.commit"] == "false"
        assert conf["auto.offset.reset"] == "earliest"

    def test_transactional_config(self):
        cfg = Config(transactional_id="test-txn-0")
        conf = cfg.to_transactional_config()
        assert conf["acks"] == "all"
        assert conf["transactional.id"] == "test-txn-0"
        assert conf["enable.idempotence"] == "true"

    def test_consumer_config_cooperative_sticky(self):
        cfg = Config(consumer_group="test-group")
        conf = cfg.to_consumer_config()
        assert conf["partition.assignment.strategy"] == "cooperative-sticky"

    def test_producer_config_linger_batch(self):
        cfg = Config(linger_ms=10, batch_size=32768, max_message_bytes=2097152)
        conf = cfg.to_producer_config()
        assert conf["linger.ms"] == "10"
        assert conf["batch.size"] == "32768"
        assert conf["message.max.bytes"] == "2097152"

    def test_transactional_config_requires_id(self):
        cfg = Config()
        with pytest.raises(ValueError, match="transactional_id is required"):
            cfg.to_transactional_config()


class TestConfigNewEnvVars:
    def test_new_fields_from_env(self, monkeypatch):
        monkeypatch.setenv("NEONLINK_LINGER_MS", "10")
        monkeypatch.setenv("NEONLINK_BATCH_SIZE", "32768")
        monkeypatch.setenv("NEONLINK_MAX_MESSAGE_BYTES", "2097152")
        monkeypatch.setenv("NEONLINK_MAX_BUFFERED_RECORDS", "500")

        cfg = new_config_from_env()
        assert cfg.linger_ms == 10
        assert cfg.batch_size == 32768
        assert cfg.max_message_bytes == 2097152
        assert cfg.max_buffered_records == 500

    def test_defaults(self):
        cfg = Config()
        assert cfg.linger_ms == 5
        assert cfg.batch_size == 16384
        assert cfg.max_message_bytes == 1048576
        assert cfg.max_buffered_records == 500


class TestPayloadValidation:
    def test_payload_too_large(self):
        from neonlink.errors import PayloadTooLargeError

        cfg = Config(max_message_bytes=100)
        p = object.__new__(type("FakeProducer", (), {
            "_max_message_bytes": 100,
            "_check_payload_size": lambda self, v: None,
        }))
        # Direct check: the error type exists and is raised correctly
        with pytest.raises(PayloadTooLargeError):
            raise PayloadTooLargeError("too big")
