"""Smaller-provider schema coverage. One happy path + one invalid path
per field is enough to lock in the contract; the validator behaviour
itself is covered exhaustively in validator_test.py."""

import pytest

from prowler.config.schema.registry import SCHEMAS
from prowler.config.schema.validator import validate_provider_config


def _validate(provider, raw):
    return validate_provider_config(provider, raw, SCHEMAS[provider])


class Test_Azure_Schema:
    @pytest.mark.parametrize("level", ["Low", "Medium", "High", "Critical"])
    def test_defender_risk_level_valid_values(self, level):
        assert _validate(
            "azure", {"defender_attack_path_minimal_risk_level": level}
        ) == {"defender_attack_path_minimal_risk_level": level}

    def test_defender_risk_level_lowercase_dropped(self):
        # Case matters: the matching check uses Title-case comparison.
        assert (
            _validate("azure", {"defender_attack_path_minimal_risk_level": "high"})
            == {}
        )

    def test_apim_threshold_in_range(self):
        out = _validate("azure", {"apim_threat_detection_llm_jacking_threshold": 0.1})
        assert out == {"apim_threat_detection_llm_jacking_threshold": 0.1}

    def test_apim_threshold_out_of_range(self):
        out = _validate("azure", {"apim_threat_detection_llm_jacking_threshold": 1.5})
        assert out == {}

    def test_vm_backup_retention_must_be_positive(self):
        assert _validate("azure", {"vm_backup_min_daily_retention_days": 7}) == {
            "vm_backup_min_daily_retention_days": 7
        }
        assert _validate("azure", {"vm_backup_min_daily_retention_days": 0}) == {}
        assert _validate("azure", {"vm_backup_min_daily_retention_days": -1}) == {}


class Test_GCP_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "mig_min_zones": 2,
            "max_snapshot_age_days": 90,
            "max_unused_account_days": 180,
            "storage_min_retention_days": 90,
        }
        assert _validate("gcp", raw) == raw

    def test_zero_zone_count_dropped(self):
        assert _validate("gcp", {"mig_min_zones": 0}) == {}


class Test_Kubernetes_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "audit_log_maxbackup": 10,
            "audit_log_maxsize": 100,
            "audit_log_maxage": 30,
        }
        assert _validate("kubernetes", raw) == raw

    def test_negative_audit_log_dropped(self):
        assert _validate("kubernetes", {"audit_log_maxage": -1}) == {}


class Test_M365_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "sign_in_frequency": 4,
            "recommended_mailtips_large_audience_threshold": 25,
            "audit_log_age": 90,
        }
        assert _validate("m365", raw) == raw

    def test_negative_audit_log_age_dropped(self):
        assert _validate("m365", {"audit_log_age": -10}) == {}


class Test_GitHub_Schema:
    def test_valid_threshold(self):
        assert _validate("github", {"inactive_not_archived_days_threshold": 180}) == {
            "inactive_not_archived_days_threshold": 180
        }

    def test_zero_threshold_dropped(self):
        assert _validate("github", {"inactive_not_archived_days_threshold": 0}) == {}


class Test_MongoDBAtlas_Schema:
    def test_valid(self):
        assert _validate(
            "mongodbatlas", {"max_service_account_secret_validity_hours": 8}
        ) == {"max_service_account_secret_validity_hours": 8}

    def test_invalid_negative(self):
        assert (
            _validate("mongodbatlas", {"max_service_account_secret_validity_hours": -1})
            == {}
        )


class Test_Cloudflare_Schema:
    def test_zero_retries_allowed(self):
        # 0 is explicitly documented as "disable retries" in config.yaml.
        assert _validate("cloudflare", {"max_retries": 0}) == {"max_retries": 0}

    def test_positive_retries_allowed(self):
        assert _validate("cloudflare", {"max_retries": 3}) == {"max_retries": 3}

    def test_negative_retries_dropped(self):
        assert _validate("cloudflare", {"max_retries": -1}) == {}


class Test_Okta_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "okta_max_session_idle_minutes": 15,
            "okta_max_session_lifetime_minutes": 18 * 60,
            "okta_admin_console_idle_timeout_max_minutes": 15,
            "okta_user_inactivity_max_days": 35,
            "okta_dod_approved_ca_issuer_patterns": [r"\bOU=DoD\b", r"\bOU=ECA\b"],
        }
        assert _validate("okta", raw) == raw

    def test_zero_idle_minutes_dropped(self):
        assert _validate("okta", {"okta_max_session_idle_minutes": 0}) == {}

    def test_negative_inactivity_days_dropped(self):
        assert _validate("okta", {"okta_user_inactivity_max_days": -1}) == {}

    def test_full_rate_limit_config_round_trip(self):
        raw = {
            "okta_requests_per_second": 4.0,
            "okta_max_retries": 5,
            "okta_request_timeout": 300,
        }
        assert _validate("okta", raw) == raw

    def test_requests_per_second_zero_allowed(self):
        # 0 is documented as "disable throttling" in config.yaml.
        assert _validate("okta", {"okta_requests_per_second": 0}) == {
            "okta_requests_per_second": 0
        }

    def test_requests_per_second_sub_one_allowed(self):
        assert _validate("okta", {"okta_requests_per_second": 0.5}) == {
            "okta_requests_per_second": 0.5
        }

    def test_requests_per_second_floor_allowed(self):
        # 0.1 is the lowest non-zero rate accepted.
        assert _validate("okta", {"okta_requests_per_second": 0.1}) == {
            "okta_requests_per_second": 0.1
        }

    def test_requests_per_second_below_floor_dropped(self):
        # A tiny rate (e.g. 0.001 -> ~1000s/request) would make scans take
        # days or years; it must be rejected, not silently honoured.
        assert _validate("okta", {"okta_requests_per_second": 0.001}) == {}
        assert _validate("okta", {"okta_requests_per_second": 0.05}) == {}

    def test_requests_per_second_above_max_dropped(self):
        assert _validate("okta", {"okta_requests_per_second": 101}) == {}

    def test_requests_per_second_negative_dropped(self):
        assert _validate("okta", {"okta_requests_per_second": -1}) == {}

    def test_max_retries_zero_allowed(self):
        assert _validate("okta", {"okta_max_retries": 0}) == {"okta_max_retries": 0}

    def test_max_retries_out_of_range_dropped(self):
        assert _validate("okta", {"okta_max_retries": -1}) == {}
        assert _validate("okta", {"okta_max_retries": 11}) == {}

    def test_request_timeout_zero_allowed(self):
        # 0 is documented as "disable the timeout" in config.yaml.
        assert _validate("okta", {"okta_request_timeout": 0}) == {
            "okta_request_timeout": 0
        }

    def test_request_timeout_in_range_allowed(self):
        assert _validate("okta", {"okta_request_timeout": 300}) == {
            "okta_request_timeout": 300
        }

    def test_request_timeout_out_of_range_dropped(self):
        assert _validate("okta", {"okta_request_timeout": -1}) == {}
        assert _validate("okta", {"okta_request_timeout": 3601}) == {}

    def test_non_numeric_value_dropped(self):
        # A typo'd string must not flow through to the limiter (it would crash
        # the `> 0` comparison during provider init).
        assert _validate("okta", {"okta_requests_per_second": "fast"}) == {}


class Test_Vercel_Schema:
    def test_owner_percentage_in_range(self):
        assert _validate("vercel", {"max_owner_percentage": 20}) == {
            "max_owner_percentage": 20
        }
        assert _validate("vercel", {"max_owner_percentage": 1}) == {
            "max_owner_percentage": 1
        }
        assert _validate("vercel", {"max_owner_percentage": 50}) == {
            "max_owner_percentage": 50
        }

    def test_owner_percentage_over_max_dropped(self):
        # Tightened to 1..50 — anything above (incl. previous 100) is dropped.
        assert _validate("vercel", {"max_owner_percentage": 51}) == {}
        assert _validate("vercel", {"max_owner_percentage": 150}) == {}

    def test_owner_percentage_zero_or_negative_dropped(self):
        # 0 is no longer a valid configuration (defeats PoLP signal).
        assert _validate("vercel", {"max_owner_percentage": 0}) == {}
        assert _validate("vercel", {"max_owner_percentage": -1}) == {}

    def test_full_default_config_round_trip(self):
        raw = {
            "stable_branches": ["main", "master"],
            "days_to_expire_threshold": 7,
            "stale_token_threshold_days": 90,
            "stale_invitation_threshold_days": 30,
            "max_owner_percentage": 20,
            "max_owners": 3,
            "secret_suffixes": ["_KEY", "_SECRET", "_TOKEN"],
        }
        assert _validate("vercel", raw) == raw


class Test_AlibabaCloud_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "max_cluster_check_days": 7,
            "max_console_access_days": 90,
            "min_log_retention_days": 365,
            "min_rds_audit_retention_days": 180,
        }
        assert _validate("alibabacloud", raw) == raw

    def test_zero_cluster_check_days_dropped(self):
        assert _validate("alibabacloud", {"max_cluster_check_days": 0}) == {}

    def test_console_access_below_min_dropped(self):
        # 30 is the documented floor; anything below produces false positives.
        assert _validate("alibabacloud", {"max_console_access_days": 29}) == {}


class Test_OpenStack_Schema:
    def test_valid_values_round_trip(self):
        raw = {
            "image_sharing_threshold": 5,
            "secrets_ignore_patterns": ["AKIA[0-9A-Z]{16}"],
        }
        assert _validate("openstack", raw) == raw

    def test_zero_threshold_dropped(self):
        assert _validate("openstack", {"image_sharing_threshold": 0}) == {}
