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

namespace SatelliteProvider.Services.RouteManagement;

public class RoutePointGraphBuilder
{
    private readonly double _maxPointSpacingMeters;

    public RoutePointGraphBuilder(IOptions<ProcessingConfig> processingConfig)
    {
        ArgumentNullException.ThrowIfNull(processingConfig);
        _maxPointSpacingMeters = processingConfig.Value.MaxRoutePointSpacingMeters;
    }

    public RoutePointGraph Build(IReadOnlyList<RoutePoint> userPoints)
    {
        ArgumentNullException.ThrowIfNull(userPoints);
        if (userPoints.Count < 2)
        {
            throw new ArgumentException("Route must have at least 2 points", nameof(userPoints));
        }

        var allPoints = new List<RoutePointDto>();
        var totalDistance = 0.0;
        var sequenceNumber = 0;

        for (int segmentIndex = 0; segmentIndex < userPoints.Count; segmentIndex++)
        {
            var current = userPoints[segmentIndex];
            var isStart = segmentIndex == 0;
            var isEnd = segmentIndex == userPoints.Count - 1;

            var currentGeo = new GeoPoint(current.Latitude, current.Longitude);

            double? distanceFromPrevious = null;
            if (allPoints.Count > 0)
            {
                var prev = allPoints[^1];
                var prevGeo = new GeoPoint(prev.Latitude, prev.Longitude);
                distanceFromPrevious = GeoUtils.CalculateDistance(prevGeo, currentGeo);
                totalDistance += distanceFromPrevious.Value;
            }

            allPoints.Add(new RoutePointDto
            {
                Latitude = current.Latitude,
                Longitude = current.Longitude,
                PointType = isStart ? RoutePointType.Start : (isEnd ? RoutePointType.End : RoutePointType.Action),
                SequenceNumber = sequenceNumber++,
                SegmentIndex = segmentIndex,
                DistanceFromPrevious = distanceFromPrevious,
            });

            if (isEnd)
            {
                continue;
            }

            var next = userPoints[segmentIndex + 1];
            var nextGeo = new GeoPoint(next.Latitude, next.Longitude);
            var intermediates = GeoUtils.CalculateIntermediatePoints(currentGeo, nextGeo, _maxPointSpacingMeters);

            foreach (var intermediateGeo in intermediates)
            {
                var prev = allPoints[^1];
                var prevGeo = new GeoPoint(prev.Latitude, prev.Longitude);
                var distFromPrev = GeoUtils.CalculateDistance(prevGeo, intermediateGeo);
                totalDistance += distFromPrev;

                allPoints.Add(new RoutePointDto
                {
                    Latitude = intermediateGeo.Lat,
                    Longitude = intermediateGeo.Lon,
                    PointType = RoutePointType.Intermediate,
                    SequenceNumber = sequenceNumber++,
                    SegmentIndex = segmentIndex,
                    DistanceFromPrevious = distFromPrev,
                });
            }
        }

        return new RoutePointGraph(allPoints, totalDistance);
    }
}

public record RoutePointGraph(IReadOnlyList<RoutePointDto> Points, double TotalDistanceMeters);
