"""Tests for parameter validation (issue #7)."""

import tempfile
from unittest.mock import patch

import pytest

from pywrkr.main import _build_parser, _parse_and_validate_args
from pywrkr.traffic_profiles import (
    CsvProfile,
    SawtoothProfile,
    SineProfile,
    SpikeProfile,
    SquareProfile,
    StepProfile,
    parse_traffic_profile,
)

# ---------------------------------------------------------------------------
# Traffic profile constructor validation
# ---------------------------------------------------------------------------


class TestSineProfileValidation:
    def test_min_factor_below_zero(self):
        with pytest.raises(ValueError, match="min_factor must be between 0 and 1"):
            SineProfile(min_factor=-0.1)

    def test_min_factor_above_one(self):
        with pytest.raises(ValueError, match="min_factor must be between 0 and 1"):
            SineProfile(min_factor=1.5)

    def test_min_factor_zero_ok(self):
        p = SineProfile(min_factor=0.0)
        assert p.min_factor == 0.0

    def test_min_factor_one_ok(self):
        p = SineProfile(min_factor=1.0)
        assert p.min_factor == 1.0

    def test_min_factor_default_ok(self):
        p = SineProfile()
        assert p.min_factor == 0.1


class TestSpikeProfileValidation:
    def test_interval_zero(self):
        with pytest.raises(ValueError, match="interval must be greater than 0"):
            SpikeProfile(interval=0)

    def test_interval_negative(self):
        with pytest.raises(ValueError, match="interval must be greater than 0"):
            SpikeProfile(interval=-5)

    def test_interval_positive_ok(self):
        p = SpikeProfile(interval=0.1)
        assert p.interval == 0.1


class TestSawtoothProfileValidation:
    def test_min_factor_below_zero(self):
        with pytest.raises(ValueError, match="min_factor must be between 0 and 1"):
            SawtoothProfile(min_factor=-0.1)

    def test_min_factor_above_one(self):
        with pytest.raises(ValueError, match="min_factor must be between 0 and 1"):
            SawtoothProfile(min_factor=1.5)

    def test_min_factor_boundary_ok(self):
        assert SawtoothProfile(min_factor=0.0).min_factor == 0.0
        assert SawtoothProfile(min_factor=1.0).min_factor == 1.0


class TestSquareProfileValidation:
    def test_low_factor_below_zero(self):
        with pytest.raises(ValueError, match="low_factor must be between 0 and 1"):
            SquareProfile(low_factor=-0.5)

    def test_low_factor_above_one(self):
        with pytest.raises(ValueError, match="low_factor must be between 0 and 1"):
            SquareProfile(low_factor=2.0)

    def test_low_factor_boundary_ok(self):
        assert SquareProfile(low_factor=0.0).low_factor == 0.0
        assert SquareProfile(low_factor=1.0).low_factor == 1.0


class TestStepProfileValidation:
    def test_negative_level(self):
        with pytest.raises(ValueError, match="levels must be non-negative"):
            StepProfile(levels=[100, -50, 200])

    def test_zero_level_ok(self):
        p = StepProfile(levels=[0, 100, 200])
        assert p.levels == [0, 100, 200]

    def test_all_positive_ok(self):
        p = StepProfile(levels=[100, 500, 1000])
        assert p.levels == [100, 500, 1000]

    def test_empty_levels(self):
        with pytest.raises(ValueError, match="at least one level"):
            StepProfile(levels=[])


# ---------------------------------------------------------------------------
# CLI argument validation (via _parse_and_validate_args)
# ---------------------------------------------------------------------------


def _parse(cli_args: list[str]):
    """Helper: parse CLI args and run validation, returning (config, args)."""
    parser = _build_parser()
    args = parser.parse_args(cli_args)
    return _parse_and_validate_args(parser, args)


class TestRateValidation:
    def test_rate_zero(self):
        with pytest.raises(SystemExit):
            _parse(["--rate", "0", "-d", "10", "http://localhost/"])

    def test_rate_negative(self):
        with pytest.raises(SystemExit):
            _parse(["--rate", "-1", "-d", "10", "http://localhost/"])

    def test_rate_positive_ok(self):
        config, _ = _parse(["--rate", "100", "-d", "10", "http://localhost/"])
        assert config.rate == 100


class TestRampUpValidation:
    def test_ramp_up_exceeds_duration(self):
        with pytest.raises(SystemExit):
            _parse(["-u", "10", "-d", "30", "--ramp-up", "30", "http://localhost/"])

    def test_ramp_up_exceeds_duration_greater(self):
        with pytest.raises(SystemExit):
            _parse(["-u", "10", "-d", "30", "--ramp-up", "60", "http://localhost/"])

    def test_ramp_up_less_than_duration_ok(self):
        config, _ = _parse(["-u", "10", "-d", "30", "--ramp-up", "10", "http://localhost/"])
        assert config.ramp_up == 10


class TestAutofindValidation:
    def test_max_users_less_than_start_users(self):
        with pytest.raises(SystemExit):
            _parse(["--autofind", "--start-users", "100", "--max-users", "50", "http://localhost/"])

    def test_max_users_equal_start_users(self):
        with pytest.raises(SystemExit):
            _parse(
                ["--autofind", "--start-users", "100", "--max-users", "100", "http://localhost/"]
            )

    def test_step_multiplier_one(self):
        with pytest.raises(SystemExit):
            _parse(["--autofind", "--step-multiplier", "1.0", "http://localhost/"])

    def test_step_multiplier_below_one(self):
        with pytest.raises(SystemExit):
            _parse(["--autofind", "--step-multiplier", "0.5", "http://localhost/"])

    def test_valid_autofind_ok(self):
        # Should not raise -- but autofind actually runs, so we just test
        # that validation passes by checking no SystemExit before run_autofind.
        # We mock run_autofind to prevent actual execution.
        with patch("pywrkr.main.run_autofind"):
            config, args = _parse(
                [
                    "--autofind",
                    "--start-users",
                    "10",
                    "--max-users",
                    "100",
                    "--step-multiplier",
                    "2.0",
                    "http://localhost/",
                ]
            )
            assert args.start_users == 10
            assert args.max_users == 100
            assert args.step_multiplier == 2.0


# ---------------------------------------------------------------------------
# CsvProfile validation
# ---------------------------------------------------------------------------


class TestCsvProfileValidation:
    def test_empty_csv_raises(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write("")
            path = f.name
        with pytest.raises(ValueError, match="empty"):
            CsvProfile(path)

    def test_header_only_csv_raises(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write("time_sec,rate\n")
            path = f.name
        with pytest.raises(ValueError, match="No data points"):
            CsvProfile(path)

    def test_single_column_rows_skipped(self):
        """Rows with fewer than 2 columns are silently skipped."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write("time_sec,rate\n0,100\nbadrow\n60,500\n")
            path = f.name
        p = CsvProfile(path)
        assert len(p._times) == 2

    def test_factor_header_detected(self):
        """'factor' header should trigger multiplier mode, same as 'multiplier'."""
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write("time_sec,factor\n0,0.5\n60,2.0\n")
            path = f.name
        p = CsvProfile(path)
        assert p._is_multiplier is True
        assert p.rate_at(0, 60, 100) == pytest.approx(50.0)

    def test_non_numeric_value_raises(self):
        with tempfile.NamedTemporaryFile(mode="w", suffix=".csv", delete=False) as f:
            f.write("time_sec,rate\n0,abc\n")
            path = f.name
        with pytest.raises(ValueError):
            CsvProfile(path)

    def test_file_not_found_raises(self):
        with pytest.raises(FileNotFoundError):
            CsvProfile("/nonexistent/path/traffic.csv")


# ---------------------------------------------------------------------------
# parse_traffic_profile validation
# ---------------------------------------------------------------------------


class TestParseTrafficProfileValidation:
    def test_step_empty_params_raises(self):
        with pytest.raises(ValueError, match="requires levels"):
            parse_traffic_profile("step:")

    def test_non_keyvalue_param_raises(self):
        """Bare params without '=' should raise for non-step profiles."""
        with pytest.raises(ValueError, match="expected key=value"):
            parse_traffic_profile("sine:badparam")

    def test_non_float_param_value_raises(self):
        with pytest.raises(ValueError, match="Invalid parameter"):
            parse_traffic_profile("sine:cycles=notanumber")

    def test_csv_whitespace_only_path_raises(self):
        with pytest.raises(ValueError, match="file path"):
            parse_traffic_profile("csv:")

    def test_unknown_profile_lists_available(self):
        with pytest.raises(ValueError, match="Available:"):
            parse_traffic_profile("nope")
