using Microsoft.Extensions.Options;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.Common.DTO;
using SatelliteProvider.Common.Utils;

namespace SatelliteProvider.Services.RouteManagement.TileProvision;

public sealed class RouteTileExpander
{
    private readonly RoutePointGraphBuilder _pointGraphBuilder;
    private readonly GeofenceGridCalculator _geofenceGridCalculator;

    public RouteTileExpander(
        RoutePointGraphBuilder pointGraphBuilder,
        GeofenceGridCalculator geofenceGridCalculator)
    {
        _pointGraphBuilder = pointGraphBuilder;
        _geofenceGridCalculator = geofenceGridCalculator;
    }

    public IReadOnlyList<RouteTileCandidate> Expand(
        IReadOnlyList<(double Lat, double Lon)> waypoints,
        double regionSizeMeters,
        int zoom,
        IReadOnlyList<IReadOnlyList<(double Lat, double Lon)>> geofenceVertices,
        bool includeGeofenceTiles)
    {
        if (waypoints.Count < 2)
        {
            throw new ArgumentException("Route must have at least 2 waypoints", nameof(waypoints));
        }

        if (regionSizeMeters <= 0)
        {
            throw new ArgumentOutOfRangeException(nameof(regionSizeMeters), "Region size must be positive");
        }

        var routePoints = waypoints
            .Select(w => new RoutePoint { Latitude = w.Lat, Longitude = w.Lon })
            .ToList();
        var graph = _pointGraphBuilder.Build(routePoints);

        var tiles = new Dictionary<(int Z, int X, int Y), uint>();

        for (var priority = 0; priority < graph.Points.Count; priority++)
        {
            var point = graph.Points[priority];
            AddCorridorTiles(
                tiles,
                new GeoPoint(point.Latitude, point.Longitude),
                regionSizeMeters,
                zoom,
                (uint)priority);
        }

        if (includeGeofenceTiles)
        {
            var geofencePriority = (uint)graph.Points.Count;
            foreach (var vertices in geofenceVertices)
            {
                if (vertices.Count < 3)
                {
                    continue;
                }

                var minLat = vertices.Min(v => v.Lat);
                var maxLat = vertices.Max(v => v.Lat);
                var minLon = vertices.Min(v => v.Lon);
                var maxLon = vertices.Max(v => v.Lon);

                var northWest = new GeoPoint(maxLat, minLon);
                var southEast = new GeoPoint(minLat, maxLon);
                var centers = _geofenceGridCalculator.GenerateRegions(northWest, southEast, regionSizeMeters);

                foreach (var center in centers)
                {
                    AddCorridorTiles(tiles, center, regionSizeMeters, zoom, geofencePriority);
                }

                geofencePriority++;
            }
        }

        return tiles
            .OrderBy(t => t.Value)
            .ThenBy(t => t.Key.Y)
            .ThenBy(t => t.Key.X)
            .Select(t => new RouteTileCandidate(t.Key.Z, t.Key.X, t.Key.Y, t.Value))
            .ToList();
    }

    private static void AddCorridorTiles(
        Dictionary<(int Z, int X, int Y), uint> tiles,
        GeoPoint center,
        double regionSizeMeters,
        int zoom,
        uint routePriority)
    {
        var radiusMeters = regionSizeMeters / 2.0;
        var (latMin, latMax, lonMin, lonMax) = GeoUtils.GetBoundingBox(center, radiusMeters);
        var (xMin, yMin) = GeoUtils.WorldToTilePos(new GeoPoint(latMax, lonMin), zoom);
        var (xMax, yMax) = GeoUtils.WorldToTilePos(new GeoPoint(latMin, lonMax), zoom);

        for (var y = yMin; y <= yMax; y++)
        {
            for (var x = xMin; x <= xMax; x++)
            {
                var key = (zoom, x, y);
                if (!tiles.TryGetValue(key, out var existing) || routePriority < existing)
                {
                    tiles[key] = routePriority;
                }
            }
        }
    }
}
