"""
Test fixtures and utilities for walkingpandas tests.

Provides dynamic Parquet file generation with various edge cases
for testing without committing large files to the repository.
"""

import pytest
import pandas as pd
import geopandas as gpd
import tempfile
import shutil
from pathlib import Path
from shapely.geometry import Point, LineString
import json
from datetime import datetime, timedelta

from walkingpandas import Network, WalkFrame


# Node mapping: A=0, B=1, C=2, D=3, E=4
# Edge mapping: AB=0, BC=1, CD=2, DA=3, BE=4


@pytest.fixture
def temp_dir():
    """Create a temporary directory for test files."""
    temp_path = tempfile.mkdtemp()
    yield Path(temp_path)
    shutil.rmtree(temp_path)


@pytest.fixture
def sample_nodes(temp_dir):
    """Create sample nodes GeoDataFrame with integer IDs."""
    nodes_data = [
        {'node_id': 0, 'x': 0, 'y': 0},
        {'node_id': 1, 'x': 1, 'y': 0},
        {'node_id': 2, 'x': 1, 'y': 1},
        {'node_id': 3, 'x': 0, 'y': 1},
        {'node_id': 4, 'x': 2, 'y': 0},
    ]

    nodes_df = pd.DataFrame(nodes_data)
    nodes_gdf = gpd.GeoDataFrame(
        nodes_df,
        geometry=[Point(row['x'], row['y']) for _, row in nodes_df.iterrows()]
    )

    nodes_path = temp_dir / 'nodes.parquet'
    nodes_gdf.to_parquet(nodes_path)

    return nodes_gdf, nodes_path


@pytest.fixture
def sample_edges(temp_dir, sample_nodes):
    """Create sample edges GeoDataFrame with integer IDs."""
    nodes_gdf, _ = sample_nodes

    edges_data = [
        {'edge_id': 0, 'source_node': 0, 'target_node': 1},
        {'edge_id': 1, 'source_node': 1, 'target_node': 2},
        {'edge_id': 2, 'source_node': 2, 'target_node': 3},
        {'edge_id': 3, 'source_node': 3, 'target_node': 0},
        {'edge_id': 4, 'source_node': 1, 'target_node': 4},
    ]

    edges_df = pd.DataFrame(edges_data)

    def create_geometry(row):
        source = nodes_gdf[nodes_gdf['node_id'] == row['source_node']].iloc[0]
        target = nodes_gdf[nodes_gdf['node_id'] == row['target_node']].iloc[0]
        return LineString([
            (source.geometry.x, source.geometry.y),
            (target.geometry.x, target.geometry.y)
        ])

    edges_gdf = gpd.GeoDataFrame(
        edges_df,
        geometry=[create_geometry(row) for _, row in edges_df.iterrows()]
    )

    edges_path = temp_dir / 'edges.parquet'
    edges_gdf.to_parquet(edges_path)

    return edges_gdf, edges_path


@pytest.fixture
def sample_walks_simple_paths(temp_dir):
    """Create sample walks data with simple paths (integer IDs)."""
    walks_data = [
        {'walk_id': 'walk1', 'sequence_idx': 0, 'node_id': 0, 'exit_edge': 0},
        {'walk_id': 'walk1', 'sequence_idx': 1, 'node_id': 1, 'exit_edge': 1},
        {'walk_id': 'walk1', 'sequence_idx': 2, 'node_id': 2, 'exit_edge': None},

        {'walk_id': 'walk2', 'sequence_idx': 0, 'node_id': 1, 'exit_edge': 4},
        {'walk_id': 'walk2', 'sequence_idx': 1, 'node_id': 4, 'exit_edge': None},
    ]

    df = pd.DataFrame(walks_data)
    df['node_id'] = df['node_id'].astype(pd.Int64Dtype())
    df['exit_edge'] = df['exit_edge'].astype(pd.Int64Dtype())
    walks_path = temp_dir / 'walks_simple.parquet'
    df.to_parquet(walks_path, index=False)

    return df, walks_path


@pytest.fixture
def sample_walks_cycles(temp_dir):
    """Create sample walks data with cycles (integer IDs)."""
    walks_data = [
        {'walk_id': 'cycle1', 'sequence_idx': 0, 'node_id': 0, 'exit_edge': 0},
        {'walk_id': 'cycle1', 'sequence_idx': 1, 'node_id': 1, 'exit_edge': 1},
        {'walk_id': 'cycle1', 'sequence_idx': 2, 'node_id': 2, 'exit_edge': 2},
        {'walk_id': 'cycle1', 'sequence_idx': 3, 'node_id': 3, 'exit_edge': 3},
        {'walk_id': 'cycle1', 'sequence_idx': 4, 'node_id': 0, 'exit_edge': None},
    ]

    df = pd.DataFrame(walks_data)
    df['node_id'] = df['node_id'].astype(pd.Int64Dtype())
    df['exit_edge'] = df['exit_edge'].astype(pd.Int64Dtype())
    walks_path = temp_dir / 'walks_cycles.parquet'
    df.to_parquet(walks_path, index=False)

    return df, walks_path


@pytest.fixture
def sample_walks_with_time(temp_dir):
    """Create sample walks data with timestamps (integer IDs)."""
    base_time = datetime(2024, 1, 1, 8, 0, 0)

    walks_data = [
        {
            'walk_id': 'walk1', 'sequence_idx': 0, 'node_id': 0,
            'exit_edge': 0, 'timestamp': base_time,
            'dwell_time': pd.Timedelta(minutes=5),
        },
        {
            'walk_id': 'walk1', 'sequence_idx': 1, 'node_id': 1,
            'exit_edge': 1, 'timestamp': base_time + timedelta(minutes=10),
            'dwell_time': pd.Timedelta(minutes=2),
        },
        {
            'walk_id': 'walk1', 'sequence_idx': 2, 'node_id': 2,
            'exit_edge': None, 'timestamp': base_time + timedelta(minutes=15),
            'dwell_time': None,
        },
    ]

    df = pd.DataFrame(walks_data)
    df['node_id'] = df['node_id'].astype(pd.Int64Dtype())
    df['exit_edge'] = df['exit_edge'].astype(pd.Int64Dtype())
    walks_path = temp_dir / 'walks_time.parquet'
    df.to_parquet(walks_path, index=False)

    return df, walks_path


@pytest.fixture
def sample_walks_orphaned(temp_dir):
    """Create sample walks data with orphaned node references (integer IDs)."""
    walks_data = [
        {'walk_id': 'walk1', 'sequence_idx': 0, 'node_id': 0, 'exit_edge': 0},
        {'walk_id': 'walk1', 'sequence_idx': 1, 'node_id': 1, 'exit_edge': 1},
        {'walk_id': 'walk2', 'sequence_idx': 0, 'node_id': 99, 'exit_edge': 99},
        {'walk_id': 'walk2', 'sequence_idx': 1, 'node_id': 98, 'exit_edge': None},
    ]

    df = pd.DataFrame(walks_data)
    df['node_id'] = df['node_id'].astype(pd.Int64Dtype())
    df['exit_edge'] = df['exit_edge'].astype(pd.Int64Dtype())
    walks_path = temp_dir / 'walks_orphaned.parquet'
    df.to_parquet(walks_path, index=False)

    return df, walks_path


@pytest.fixture
def sample_walks_dynamic_attrs(temp_dir):
    """Create sample walks data with dynamic attributes (integer IDs)."""
    walks_data = [
        {
            'walk_id': 'walk1', 'sequence_idx': 0, 'node_id': 0,
            'exit_edge': 0,
            'dynamic_attributes': json.dumps({'purpose': 'commute', 'mode': 'walk'}),
        },
        {
            'walk_id': 'walk1', 'sequence_idx': 1, 'node_id': 1,
            'exit_edge': 1,
            'dynamic_attributes': json.dumps({'purpose': 'commute', 'mode': 'walk'}),
        },
        {
            'walk_id': 'walk2', 'sequence_idx': 0, 'node_id': 0,
            'exit_edge': 0,
            'dynamic_attributes': json.dumps({'purpose': 'leisure', 'weather': 'sunny'}),
        },
    ]

    df = pd.DataFrame(walks_data)
    df['node_id'] = df['node_id'].astype(pd.Int64Dtype())
    df['exit_edge'] = df['exit_edge'].astype(pd.Int64Dtype())
    walks_path = temp_dir / 'walks_dynamic.parquet'
    df.to_parquet(walks_path, index=False)

    return df, walks_path


@pytest.fixture
def sample_network(temp_dir, sample_nodes, sample_edges):
    """Create a Network instance from sample data."""
    _, nodes_path = sample_nodes
    _, edges_path = sample_edges

    network = Network(nodes=nodes_path, edges=edges_path)
    return network


@pytest.fixture
def sample_walkframe(temp_dir, sample_network, sample_walks_simple_paths):
    """Create a WalkFrame instance from sample data."""
    _, walks_path = sample_walks_simple_paths

    walkframe = WalkFrame(walks_path, network=sample_network)
    return walkframe


# ---------------------------------------------------------------------------
# Map-matching fixtures
# ---------------------------------------------------------------------------


@pytest.fixture
def sample_gps_csv(temp_dir):
    """Small GPS CSV with 2 trips (~5 points each) for map-matching tests."""
    rows = [
        {"trip_id": "trip_1", "timestamp": "2024-01-01 08:00:00", "lon": 8.5400, "lat": 47.3780},
        {"trip_id": "trip_1", "timestamp": "2024-01-01 08:00:05", "lon": 8.5401, "lat": 47.3781},
        {"trip_id": "trip_1", "timestamp": "2024-01-01 08:00:10", "lon": 8.5402, "lat": 47.3782},
        {"trip_id": "trip_1", "timestamp": "2024-01-01 08:00:15", "lon": 8.5403, "lat": 47.3783},
        {"trip_id": "trip_1", "timestamp": "2024-01-01 08:00:20", "lon": 8.5404, "lat": 47.3784},
        {"trip_id": "trip_2", "timestamp": "2024-01-01 09:00:00", "lon": 8.5410, "lat": 47.3790},
        {"trip_id": "trip_2", "timestamp": "2024-01-01 09:00:05", "lon": 8.5411, "lat": 47.3791},
        {"trip_id": "trip_2", "timestamp": "2024-01-01 09:00:10", "lon": 8.5412, "lat": 47.3792},
        {"trip_id": "trip_2", "timestamp": "2024-01-01 09:00:15", "lon": 8.5413, "lat": 47.3793},
    ]
    df = pd.DataFrame(rows)
    csv_path = temp_dir / "gps_sample.csv"
    df.to_csv(csv_path, index=False)
    return csv_path, df


@pytest.fixture
def sample_osm_edges_gdf():
    """Small edges GeoDataFrame mimicking OSMnx output with MultiIndex (integer IDs)."""
    n1 = (8.5400, 47.3780)
    n2 = (8.5402, 47.3782)
    n3 = (8.5404, 47.3784)
    n4 = (8.5406, 47.3780)

    edges_data = [
        {"u": 1001, "v": 1002, "key": 0, "osmid": "100", "edge_id": 0,
         "geometry": LineString([n1, n2])},
        {"u": 1002, "v": 1003, "key": 0, "osmid": "100", "edge_id": 1,
         "geometry": LineString([n2, n3])},
        {"u": 1002, "v": 1004, "key": 0, "osmid": "200", "edge_id": 2,
         "geometry": LineString([n2, n4])},
    ]
    df = pd.DataFrame(edges_data)
    gdf = gpd.GeoDataFrame(df, geometry="geometry", crs="EPSG:4326")
    gdf = gdf.set_index(["u", "v", "key"])
    return gdf


@pytest.fixture
def sample_osm_nodes_gdf():
    """Nodes GeoDataFrame matching :func:`sample_osm_edges_gdf` (integer IDs)."""
    nodes = {
        1001: Point(8.5400, 47.3780),
        1002: Point(8.5402, 47.3782),
        1003: Point(8.5404, 47.3784),
        1004: Point(8.5406, 47.3780),
    }
    gdf = gpd.GeoDataFrame(
        {"node_id": list(nodes.keys())},
        geometry=list(nodes.values()),
        index=list(nodes.keys()),
        crs="EPSG:4326",
    )
    return gdf
