"""Shared test fixtures.

Generates synthetic MME test data that mimics a realistic frontal crash test.
"""

from __future__ import annotations

from pathlib import Path

import numpy as np
import pytest

from impakt.channel.code import ChannelCode
from impakt.channel.model import (
    Channel,
    ChannelGroup,
    DummyInfo,
    ImpactConfig,
    TestData,
    TestMetadata,
    VehicleInfo,
)


@pytest.fixture
def sample_rate() -> float:
    return 20000.0


@pytest.fixture
def duration() -> float:
    return 0.2  # 200 ms


@pytest.fixture
def time_array(sample_rate: float, duration: float) -> np.ndarray:
    """Time array: -0.01s to 0.19s (10 ms pre-trigger + 190 ms event)."""
    n = int(duration * sample_rate)
    pre = int(0.01 * sample_rate)
    return np.arange(n, dtype=np.float64) / sample_rate - pre / sample_rate


@pytest.fixture
def head_accel_x(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic head X acceleration: half-sine pulse peaking at ~40g."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0) & (t <= 0.1)
    data[mask] = 40.0 * np.sin(np.pi * t[mask] / 0.1)
    # Add some noise
    data += np.random.default_rng(42).normal(0, 0.5, len(data))

    return Channel(
        name="11HEAD0000ACXA",
        code=ChannelCode.parse("11HEAD0000ACXA"),
        data=data,
        time=t,
        unit="g",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def head_accel_y(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic head Y acceleration: smaller lateral component."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0) & (t <= 0.1)
    data[mask] = 8.0 * np.sin(np.pi * t[mask] / 0.1) * np.cos(3 * np.pi * t[mask] / 0.1)
    data += np.random.default_rng(43).normal(0, 0.3, len(data))

    return Channel(
        name="11HEAD0000ACYA",
        code=ChannelCode.parse("11HEAD0000ACYA"),
        data=data,
        time=t,
        unit="g",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def head_accel_z(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic head Z acceleration."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0) & (t <= 0.1)
    data[mask] = 15.0 * np.sin(np.pi * t[mask] / 0.1)
    data += np.random.default_rng(44).normal(0, 0.3, len(data))

    return Channel(
        name="11HEAD0000ACZA",
        code=ChannelCode.parse("11HEAD0000ACZA"),
        data=data,
        time=t,
        unit="g",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def head_group(head_accel_x: Channel, head_accel_y: Channel, head_accel_z: Channel) -> ChannelGroup:
    return ChannelGroup(
        key="11HEAD0000AC_A",
        x=head_accel_x,
        y=head_accel_y,
        z=head_accel_z,
    )


@pytest.fixture
def chest_deflection_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic chest deflection: ramps to 35mm peak."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0.01) & (t <= 0.08)
    t_event = t[mask] - 0.01
    data[mask] = 35.0 * np.sin(np.pi * t_event / 0.07)

    return Channel(
        name="11CHST0000DCXA",
        code=ChannelCode.parse("11CHST0000DCXA"),
        data=data,
        time=t,
        unit="mm",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def neck_fz_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic neck Fz: tension pulse peaking at ~3000N."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0.005) & (t <= 0.06)
    t_event = t[mask] - 0.005
    data[mask] = 3000.0 * np.sin(np.pi * t_event / 0.055)

    return Channel(
        name="11NECKUP00FOZA",
        code=ChannelCode.parse("11NECKUP00FOZA"),
        data=data,
        time=t,
        unit="N",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def neck_my_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic neck My: flexion moment peaking at ~80 Nm."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0.01) & (t <= 0.07)
    t_event = t[mask] - 0.01
    data[mask] = 80.0 * np.sin(np.pi * t_event / 0.06)

    return Channel(
        name="11NECKUP00MOYA",
        code=ChannelCode.parse("11NECKUP00MOYA"),
        data=data,
        time=t,
        unit="N·m",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def femur_left_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
    """Synthetic left femur load: compressive pulse peaking at ~4500N."""
    t = time_array
    data = np.zeros_like(t)
    mask = (t >= 0.02) & (t <= 0.09)
    t_event = t[mask] - 0.02
    data[mask] = -4500.0 * np.sin(np.pi * t_event / 0.07)

    return Channel(
        name="11FEMRLE00FOZA",
        code=ChannelCode.parse("11FEMRLE00FOZA"),
        data=data,
        time=t,
        unit="N",
        sample_rate=sample_rate,
        source_test_id="TEST_001",
    )


@pytest.fixture
def sample_metadata() -> TestMetadata:
    return TestMetadata(
        test_number="TEST_001",
        vehicle=VehicleInfo(make="Toyota", model="Camry", year=2024, mass_kg=1523.0),
        dummy=DummyInfo(dummy_type="H3-50M", position="Driver", mass_kg=78.0),
        impact=ImpactConfig(
            test_type="Full Frontal",
            speed_kmh=56.3,
            barrier_type="Rigid",
            overlap_percent=100.0,
        ),
    )


@pytest.fixture
def sample_test_data(
    sample_metadata: TestMetadata,
    head_accel_x: Channel,
    head_accel_y: Channel,
    head_accel_z: Channel,
    chest_deflection_channel: Channel,
    neck_fz_channel: Channel,
    neck_my_channel: Channel,
    femur_left_channel: Channel,
) -> TestData:
    """Full synthetic test data with multiple channels."""
    channels = {
        head_accel_x.name: head_accel_x,
        head_accel_y.name: head_accel_y,
        head_accel_z.name: head_accel_z,
        chest_deflection_channel.name: chest_deflection_channel,
        neck_fz_channel.name: neck_fz_channel,
        neck_my_channel.name: neck_my_channel,
        femur_left_channel.name: femur_left_channel,
    }
    return TestData(
        test_id="TEST_001",
        metadata=sample_metadata,
        channels=channels,
    )


@pytest.fixture
def sample_mme_dir(tmp_path: Path, head_accel_x: Channel) -> Path:
    """Create a minimal MME directory structure for reader tests."""
    test_dir = tmp_path / "test_001"
    test_dir.mkdir()

    # MME.ini
    ini = test_dir / "MME.ini"
    ini.write_text(
        "[Test]\n"
        "test_number = TEST_001\n"
        "test_type = Full Frontal\n"
        "test_speed = 56.3\n"
        "\n"
        "[Vehicle]\n"
        "vehicle_make = Toyota\n"
        "vehicle_model = Camry\n"
        "vehicle_year = 2024\n"
        "\n"
        "[Dummy]\n"
        "dummy_type = H3-50M\n"
        "dummy_position = Driver\n",
        encoding="utf-8",
    )

    # Channel directory
    ch_dir = test_dir / "channels"
    ch_dir.mkdir()

    # .chn file
    chn = ch_dir / "11HEAD0000ACXA.chn"
    chn.write_text(
        "[Channel]\n"
        "channel_code = 11HEAD0000ACXA\n"
        "unit = g\n"
        "sample_rate = 20000\n"
        "num_samples = 4000\n"
        "pre_trigger = 200\n"
        "cfc = 1000\n"
        "data_format = ascii\n",
        encoding="utf-8",
    )

    # .dat file (ASCII, one value per line)
    dat = ch_dir / "11HEAD0000ACXA.dat"
    np.savetxt(str(dat), head_accel_x.data, fmt="%.6f")

    return test_dir
