import os
import csv
import json
from datetime import datetime
import argparse
from collections import defaultdict


def read_csv_file(file_path):
    """Read a CSV file and return a list of dictionaries."""
    with open(file_path, "r", encoding="utf-8") as f:
        reader = csv.DictReader(f)
        return list(reader)


def get_day_of_week_string(service_id, calendar):
    """Generate day of week string based on calendar data."""
    for cal in calendar:
        if cal["service_id"] == service_id:
            days = []
            if cal["monday"] == "1":
                days.append("Mo")
            if cal["tuesday"] == "1":
                days.append("Tu")
            if cal["wednesday"] == "1":
                days.append("We")
            if cal["thursday"] == "1":
                days.append("Th")
            if cal["friday"] == "1":
                days.append("Fr")
            if cal["saturday"] == "1":
                days.append("Sa")
            if cal["sunday"] == "1":
                days.append("Su")
            return ", ".join(days)
    return ""


def format_time(time_str):
    """Format time string from HH:MM:SS to HH:MM in 24-hour format."""
    if not time_str:
        return ""
    parts = time_str.split(":")
    return f"{parts[0]}:{parts[1]}"


def encode_coordinates(points):
    """
    Encode a list of coordinates using Google's polyline algorithm.

    Args:
        points: List of (lat, lng) tuples

    Returns:
        String containing the encoded polyline
    """
    result = []
    prev_lat = 0
    prev_lng = 0

    for lat, lng in points:
        # Convert to integer with 5 decimal places precision
        lat_e5 = int(lat * 1e5)
        lng_e5 = int(lng * 1e5)

        # Compute delta from previous point
        dlat = lat_e5 - prev_lat
        dlng = lng_e5 - prev_lng

        # Remember current point for next iteration
        prev_lat = lat_e5
        prev_lng = lng_e5

        # Encode latitude and longitude
        for value in [dlat, dlng]:
            # Handle negative values
            value = ~(value << 1) if value < 0 else (value << 1)

            # Split into 5-bit chunks
            while value >= 0x20:
                result.append(chr((0x20 | (value & 0x1F)) + 63))
                value >>= 5
            result.append(chr(value + 63))

    return "".join(result)


def convert_gtfs_to_json(gtfs_dir, output_dir):
    """Convert GTFS files to JSON files matching the required schema."""
    # Ensure output directory exists
    os.makedirs(output_dir, exist_ok=True)

    # Read GTFS files
    routes = read_csv_file(os.path.join(gtfs_dir, "routes.txt"))
    trips = read_csv_file(os.path.join(gtfs_dir, "trips.txt"))
    stop_times = read_csv_file(os.path.join(gtfs_dir, "stop_times.txt"))
    stops = read_csv_file(os.path.join(gtfs_dir, "stops.txt"))
    calendar = read_csv_file(os.path.join(gtfs_dir, "calendar.txt"))

    # Read shapes.txt if available for polylines
    shapes_file = os.path.join(gtfs_dir, "shapes.txt")
    shapes = read_csv_file(shapes_file) if os.path.exists(shapes_file) else []

    # Create lookup tables
    routes_lookup = {route["route_id"]: route for route in routes}
    stops_lookup = {stop["stop_id"]: stop for stop in stops}

    # Group stop times by trip
    trip_stop_times = defaultdict(list)
    for stop_time in stop_times:
        trip_stop_times[stop_time["trip_id"]].append(stop_time)

    # Sort stop times by sequence for each trip
    for trip_id in trip_stop_times:
        trip_stop_times[trip_id].sort(key=lambda x: int(x["stop_sequence"]))

    # Create shapes lookup
    shape_points = defaultdict(list)
    for shape in shapes:
        shape_points[shape["shape_id"]].append(
            {
                "shape_pt_sequence": shape["shape_pt_sequence"],
                "point": (float(shape["shape_pt_lat"]), float(shape["shape_pt_lon"])),
            }
        )

    # Sort shape points by sequence
    for shape_id in shape_points:
        shape_points[shape_id].sort(key=lambda x: int(x.get("shape_pt_sequence", 0)))
        shape_points[shape_id] = [shape["point"] for shape in shape_points[shape_id]]

    # Process each trip
    for trip in trips:
        trip_id = trip["trip_id"]
        route_id = trip["route_id"]
        service_id = trip["service_id"]
        shape_id = trip.get("shape_id", "")

        # Get route info
        route_info = routes_lookup.get(route_id, {})
        route_name = route_info.get("route_long_name", "")
        route_abbr = route_info.get("route_short_name", None)

        # Create stops array with times
        stops_array = []
        for stop_time in trip_stop_times.get(trip_id, []):
            stop_id = stop_time["stop_id"]
            arrival_time = format_time(stop_time["arrival_time"])

            # Get day of week string
            dotw = get_day_of_week_string(service_id, calendar)

            stop_obj = {
                "href": f"https://$base_domain/stops/{stop_id}",
                "times": [{"dotw": dotw, "time": arrival_time}],
            }
            stops_array.append(stop_obj)

        # Create polyline using Google's polyline algorithm
        polyline = ""
        if shape_id and shape_id in shape_points:
            points = shape_points[shape_id]
            polyline = encode_coordinates(points)

        # Create JSON object
        trip_json = {
            "_links": {
                "self": {"href": f"https://$base_domain/trips/{trip_id}"},
                "stops": stops_array,
            },
            "name": route_name,
            "abbr": route_abbr if route_abbr else None,
            "polyline": polyline,
        }

        # Write to JSON file
        output_file = os.path.join(output_dir, f"{trip_id}.json")
        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(trip_json, f, indent=2)

        print(f"Created {output_file}")


def main():
    parser = argparse.ArgumentParser(description="Convert GTFS files to JSON format")
    parser.add_argument("gtfs_dir", help="Directory containing GTFS files")
    parser.add_argument("output_dir", help="Directory to output JSON files")

    args = parser.parse_args()

    convert_gtfs_to_json(args.gtfs_dir, args.output_dir)
    print("Conversion complete!")


if __name__ == "__main__":
    main()
