"""Unit tests for Pydantic models and their validators."""

import pytest
from pydantic import ValidationError

from app.models.config import GlobalConfigResponse, GlobalConfigUpdate


def test_add_log_path_request_default_tail_is_true() -> None:
    """Tail defaults to True."""
    from app.models.config import AddLogPathRequest

    req = AddLogPathRequest(log_path="/var/log/app.log")
    assert req.tail is True


def test_add_log_path_request_can_be_created() -> None:
    """AddLogPathRequest can be created with valid data (no validators in model)."""
    from app.models.config import AddLogPathRequest

    req = AddLogPathRequest(log_path="/etc/passwd", tail=True)
    # Note: path validation is now in the router layer, not in the model
    assert req.log_path == "/etc/passwd"
    assert req.tail is True



# ---------------------------------------------------------------------------
# GlobalConfigUpdate and GlobalConfigResponse
# ---------------------------------------------------------------------------


def test_global_config_update_valid_log_level() -> None:
    """GlobalConfigUpdate accepts valid log levels."""
    for level in ["CRITICAL", "ERROR", "WARNING", "NOTICE", "INFO", "DEBUG"]:
        update = GlobalConfigUpdate(log_level=level)
        assert update.log_level == level


def test_global_config_update_invalid_log_level() -> None:
    """GlobalConfigUpdate rejects invalid log levels."""
    with pytest.raises(ValidationError) as exc_info:
        GlobalConfigUpdate(log_level="invalid")
    error_msg = str(exc_info.value)
    assert "CRITICAL" in error_msg


def test_global_config_update_log_level_case_sensitive() -> None:
    """GlobalConfigUpdate log_level is case-sensitive (must be uppercase)."""
    with pytest.raises(ValidationError):
        GlobalConfigUpdate(log_level="debug")

    with pytest.raises(ValidationError):
        GlobalConfigUpdate(log_level="Debug")


def test_global_config_update_can_set_log_target() -> None:
    """GlobalConfigUpdate can set log_target (validation moved to router)."""
    # Note: path validation for log_target is now in the router layer
    update = GlobalConfigUpdate(log_target="/etc/passwd")
    assert update.log_target == "/etc/passwd"


def test_global_config_update_special_log_targets() -> None:
    """GlobalConfigUpdate accepts special log target values (no validation in model)."""
    for target in ["STDOUT", "STDERR", "SYSLOG"]:
        update = GlobalConfigUpdate(log_target=target)
        assert update.log_target == target


def test_global_config_update_none_fields() -> None:
    """GlobalConfigUpdate allows None for optional fields."""
    update = GlobalConfigUpdate()
    assert update.log_level is None
    assert update.log_target is None


def test_global_config_response_log_level() -> None:
    """GlobalConfigResponse enforces valid log levels."""
    response = GlobalConfigResponse(
        log_level="INFO",
        log_target="STDOUT",
        db_purge_age=86400,
        db_max_matches=10,
    )
    assert response.log_level == "INFO"

    with pytest.raises(ValidationError):
        GlobalConfigResponse(
            log_level="invalid",
            log_target="STDOUT",
            db_purge_age=86400,
            db_max_matches=10,
        )


def test_global_config_response_log_target_special() -> None:
    """GlobalConfigResponse accepts special log targets."""
    response = GlobalConfigResponse(
        log_level="INFO",
        log_target="SYSLOG",
        db_purge_age=86400,
        db_max_matches=10,
    )
    assert response.log_target == "SYSLOG"


def test_global_config_response_log_target_path() -> None:
    """GlobalConfigResponse accepts any log target path (validation in router)."""
    response = GlobalConfigResponse(
        log_level="INFO",
        log_target="/root/secret.log",
        db_purge_age=86400,
        db_max_matches=10,
    )
    assert response.log_target == "/root/secret.log"


# ---------------------------------------------------------------------------
# LoginRequest and SetupRequest password validation
# ---------------------------------------------------------------------------


def test_login_request_password_accepted_at_72_bytes() -> None:
    """LoginRequest accepts passwords exactly 72 bytes long."""
    from app.models.auth import LoginRequest

    password_72 = "a" * 72
    req = LoginRequest(password=password_72)
    assert req.password == password_72


def test_login_request_password_rejected_over_72_bytes() -> None:
    """LoginRequest rejects passwords exceeding 72 bytes."""
    from app.models.auth import LoginRequest

    password_73 = "a" * 73
    with pytest.raises(ValidationError) as exc_info:
        LoginRequest(password=password_73)
    error_msg = str(exc_info.value)
    assert "at most 72 characters" in error_msg or "max_length" in error_msg


def test_setup_request_master_password_accepted_at_72_bytes() -> None:
    """SetupRequest accepts master_password exactly 72 bytes long."""
    from app.models.setup import SetupRequest

    password_72 = "Password1!" + "a" * 61  # 72 chars total with required complexity
    req = SetupRequest(master_password=password_72)
    assert req.master_password == password_72


def test_setup_request_master_password_rejected_over_72_bytes() -> None:
    """SetupRequest rejects master_password exceeding 72 bytes."""
    from app.models.setup import SetupRequest

    password_73 = "Password1!" + "a" * 63  # 73 chars total
    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password=password_73)
    error_msg = str(exc_info.value)
    assert "at most 72 characters" in error_msg or "string_too_long" in error_msg


def test_setup_request_master_password_min_length_still_enforced() -> None:
    """SetupRequest still enforces minimum password length (8 characters)."""
    from app.models.setup import SetupRequest

    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password="Short1!")
    error_msg = str(exc_info.value)
    assert "8 characters" in error_msg


def test_setup_request_master_password_complexity_still_enforced() -> None:
    """SetupRequest still enforces password complexity requirements."""
    from app.models.setup import SetupRequest

    # Missing uppercase
    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password="password123!")
    assert "uppercase" in str(exc_info.value)

    # Missing number
    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password="Password!")
    assert "number" in str(exc_info.value)

    # Missing special character
    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password="Password1")
    assert "special character" in str(exc_info.value)


def test_setup_request_rejects_common_passwords() -> None:
    """SetupRequest rejects common passwords that pass all other complexity checks."""
    from app.models.setup import SetupRequest

    # Passw0rd! passes length (10), uppercase, digit, special checks but is too common
    with pytest.raises(ValidationError) as exc_info:
        SetupRequest(master_password="Passw0rd!")
    assert "too common" in str(exc_info.value).lower()


def test_setup_request_accepts_valid_unique_password() -> None:
    """SetupRequest accepts a password that meets all requirements and is not common."""
    from app.models.setup import SetupRequest

    req = SetupRequest(master_password="MyV3ryStr0ng!P@ssw0rd")
    assert req.master_password == "MyV3ryStr0ng!P@ssw0rd"


def test_setup_request_rejects_short_common_passwords() -> None:
    """SetupRequest rejects short common passwords (rejected for length, not common check)."""
    from app.models.setup import SetupRequest

    for password in ["letmein", "admin", "qwerty", "shadow"]:
        with pytest.raises(ValidationError) as exc_info:
            SetupRequest(master_password=password)
        # These fail the minimum length check first
        assert "at least 8 characters" in str(exc_info.value)


# ---------------------------------------------------------------------------
# DashboardBanItem country_code validator
# ---------------------------------------------------------------------------


def test_dashboard_ban_item_country_code_null() -> None:
    """DashboardBanItem accepts None for country_code."""
    from app.models.ban import DashboardBanItem

    item = DashboardBanItem(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country_code=None,
    )
    assert item.country_code is None


def test_dashboard_ban_item_country_code_valid() -> None:
    """DashboardBanItem accepts a valid 2-char uppercase country code."""
    from app.models.ban import DashboardBanItem

    item = DashboardBanItem(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country_code="US",
    )
    assert item.country_code == "US"


def test_dashboard_ban_item_country_code_empty_string_coerced_to_none() -> None:
    """DashboardBanItem coerces empty-string country_code to None."""
    from app.models.ban import DashboardBanItem

    item = DashboardBanItem(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country_code="",
    )
    assert item.country_code is None


# ---------------------------------------------------------------------------
# ActiveBan country validator
# ---------------------------------------------------------------------------


def test_active_ban_country_null() -> None:
    """ActiveBan accepts None for country."""
    from app.models.ban import ActiveBan

    ban = ActiveBan(ip="1.2.3.4", jail="sshd", country=None)
    assert ban.country is None


def test_active_ban_country_valid() -> None:
    """ActiveBan accepts a valid country code."""
    from app.models.ban import ActiveBan

    ban = ActiveBan(ip="1.2.3.4", jail="sshd", country="DE")
    assert ban.country == "DE"


def test_active_ban_country_empty_string_coerced_to_none() -> None:
    """ActiveBan coerces empty-string country to None."""
    from app.models.ban import ActiveBan

    ban = ActiveBan(ip="1.2.3.4", jail="sshd", country="")
    assert ban.country is None


# ---------------------------------------------------------------------------
# Ban country validator
# ---------------------------------------------------------------------------


def test_ban_country_null() -> None:
    """Ban accepts None for country."""
    from app.models.ban import Ban

    ban = Ban(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country=None,
    )
    assert ban.country is None


def test_ban_country_valid() -> None:
    """Ban accepts a valid country code."""
    from app.models.ban import Ban

    ban = Ban(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country="FR",
    )
    assert ban.country == "FR"


def test_ban_country_empty_string_coerced_to_none() -> None:
    """Ban coerces empty-string country to None."""
    from app.models.ban import Ban

    ban = Ban(
        ip="1.2.3.4",
        jail="sshd",
        banned_at="2026-04-28T07:00:00+00:00",
        ban_count=1,
        origin="selfblock",
        country="",
    )
    assert ban.country is None


# ---------------------------------------------------------------------------
# ConfFileCreateRequest reserved jail name validation
# ---------------------------------------------------------------------------


def test_conffile_create_request_rejects_reserved_jail_name() -> None:
    """ConfFileCreateRequest rejects fail2ban reserved jail names."""
    from app.models.file_config import ConfFileCreateRequest

    for reserved in ["all", "status", "purge", "start", "stop", "reload", "restart"]:
        with pytest.raises(ValidationError) as exc_info:
            ConfFileCreateRequest(name=reserved, content="[my jail]")
        assert "reserved" in str(exc_info.value).lower()


def test_conffile_create_request_accepts_valid_jail_name() -> None:
    """ConfFileCreateRequest accepts valid non-reserved jail names."""
    from app.models.file_config import ConfFileCreateRequest

    req = ConfFileCreateRequest(name="sshd", content="[my jail]")
    assert req.name == "sshd"

    req = ConfFileCreateRequest(name="nginx-http-auth", content="[my jail]")
    assert req.name == "nginx-http-auth"


def test_conffile_create_request_rejects_ban_and_unban() -> None:
    """ConfFileCreateRequest rejects 'ban' and 'unban' as jail names."""
    from app.models.file_config import ConfFileCreateRequest

    for name in ["ban", "unban", "add", "del", "set", "get"]:
        with pytest.raises(ValidationError):
            ConfFileCreateRequest(name=name, content="[my jail]")
