using Dapper;
using Npgsql;
using SatelliteProvider.DataAccess.Models;

namespace SatelliteProvider.DataAccess.Repositories;

public class RouteRepository : IRouteRepository
{
    private const string ColumnList = @"id, name, description, region_size_meters as RegionSizeMeters,
                   zoom_level as ZoomLevel, total_distance_meters as TotalDistanceMeters,
                   total_points as TotalPoints, request_maps as RequestMaps,
                   maps_ready as MapsReady, create_tiles_zip as CreateTilesZip,
                   csv_file_path as CsvFilePath,
                   summary_file_path as SummaryFilePath, stitched_image_path as StitchedImagePath,
                   tiles_zip_path as TilesZipPath,
                   created_at as CreatedAt, updated_at as UpdatedAt";

    private readonly string _connectionString;

    public RouteRepository(string connectionString)
    {
        _connectionString = connectionString;
    }

    public async Task<RouteEntity?> GetByIdAsync(Guid id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = $@"
            SELECT {ColumnList}
            FROM routes
            WHERE id = @Id";

        return await connection.QuerySingleOrDefaultAsync<RouteEntity>(sql, new { Id = id });
    }

    public async Task<IEnumerable<RoutePointEntity>> GetRoutePointsAsync(Guid routeId)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            SELECT id, route_id as RouteId, sequence_number as SequenceNumber,
                   latitude, longitude, point_type as PointType, segment_index as SegmentIndex,
                   distance_from_previous as DistanceFromPrevious, created_at as CreatedAt
            FROM route_points 
            WHERE route_id = @RouteId
            ORDER BY sequence_number";

        var points = (await connection.QueryAsync<RoutePointEntity>(sql, new { RouteId = routeId })).ToList();
        return points;
    }

    public async Task<Guid> InsertRouteAsync(RouteEntity route)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            INSERT INTO routes (id, name, description, region_size_meters, zoom_level,
                              total_distance_meters, total_points, request_maps, maps_ready,
                              create_tiles_zip, csv_file_path, summary_file_path, stitched_image_path,
                              tiles_zip_path, created_at, updated_at)
            VALUES (@Id, @Name, @Description, @RegionSizeMeters, @ZoomLevel,
                    @TotalDistanceMeters, @TotalPoints, @RequestMaps, @MapsReady,
                    @CreateTilesZip, @CsvFilePath, @SummaryFilePath, @StitchedImagePath,
                    @TilesZipPath, @CreatedAt, @UpdatedAt)
            RETURNING id";

        return await connection.ExecuteScalarAsync<Guid>(sql, route);
    }

    public async Task InsertRoutePointsAsync(IEnumerable<RoutePointEntity> points)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            INSERT INTO route_points (id, route_id, sequence_number, latitude, longitude,
                                     point_type, segment_index, distance_from_previous, created_at)
            VALUES (@Id, @RouteId, @SequenceNumber, @Latitude, @Longitude,
                    @PointType, @SegmentIndex, @DistanceFromPrevious, @CreatedAt)";

        var pointsList = points.ToList();
        await connection.ExecuteAsync(sql, pointsList);
    }

    public async Task<int> UpdateRouteAsync(RouteEntity route)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            UPDATE routes 
            SET name = @Name,
                description = @Description,
                region_size_meters = @RegionSizeMeters,
                zoom_level = @ZoomLevel,
                total_distance_meters = @TotalDistanceMeters,
                total_points = @TotalPoints,
                request_maps = @RequestMaps,
                maps_ready = @MapsReady,
                create_tiles_zip = @CreateTilesZip,
                csv_file_path = @CsvFilePath,
                summary_file_path = @SummaryFilePath,
                stitched_image_path = @StitchedImagePath,
                tiles_zip_path = @TilesZipPath,
                updated_at = @UpdatedAt
            WHERE id = @Id";

        return await connection.ExecuteAsync(sql, route);
    }

    public async Task<int> DeleteRouteAsync(Guid id)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = "DELETE FROM routes WHERE id = @Id";
        return await connection.ExecuteAsync(sql, new { Id = id });
    }

    public async Task LinkRouteToRegionAsync(Guid routeId, Guid regionId, bool isGeofence = false, int? geofencePolygonIndex = null)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            INSERT INTO route_regions (route_id, region_id, is_geofence, geofence_polygon_index, created_at)
            VALUES (@RouteId, @RegionId, @IsGeofence, @GeofencePolygonIndex, @CreatedAt)
            ON CONFLICT (route_id, region_id) DO NOTHING";

        await connection.ExecuteAsync(sql, new { RouteId = routeId, RegionId = regionId, IsGeofence = isGeofence, GeofencePolygonIndex = geofencePolygonIndex, CreatedAt = DateTime.UtcNow });
    }

    public async Task<IEnumerable<Guid>> GetRegionIdsByRouteAsync(Guid routeId)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            SELECT region_id
            FROM route_regions 
            WHERE route_id = @RouteId AND (is_geofence = false OR is_geofence IS NULL)";

        return await connection.QueryAsync<Guid>(sql, new { RouteId = routeId });
    }

    public async Task<IEnumerable<Guid>> GetGeofenceRegionIdsByRouteAsync(Guid routeId)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            SELECT region_id
            FROM route_regions 
            WHERE route_id = @RouteId AND is_geofence = true";

        return await connection.QueryAsync<Guid>(sql, new { RouteId = routeId });
    }

    public async Task<IEnumerable<RouteEntity>> GetRoutesWithPendingMapsAsync()
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = $@"
            SELECT {ColumnList}
            FROM routes
            WHERE request_maps = true AND maps_ready = false";

        return await connection.QueryAsync<RouteEntity>(sql);
    }

    public async Task<Dictionary<int, List<Guid>>> GetGeofenceRegionsByPolygonAsync(Guid routeId)
    {
        using var connection = new NpgsqlConnection(_connectionString);
        const string sql = @"
            SELECT region_id, geofence_polygon_index
            FROM route_regions 
            WHERE route_id = @RouteId AND is_geofence = true AND geofence_polygon_index IS NOT NULL
            ORDER BY geofence_polygon_index";

        var results = await connection.QueryAsync<(Guid RegionId, int PolygonIndex)>(sql, new { RouteId = routeId });

        var grouped = new Dictionary<int, List<Guid>>();
        foreach (var (regionId, polygonIndex) in results)
        {
            if (!grouped.ContainsKey(polygonIndex))
            {
                grouped[polygonIndex] = new List<Guid>();
            }
            grouped[polygonIndex].Add(regionId);
        }

        return grouped;
    }
}
