using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using SatelliteProvider.Common.Configs;
using SatelliteProvider.Common.DTO;
using SatelliteProvider.Common.Interfaces;
using SatelliteProvider.DataAccess.Models;
using SatelliteProvider.DataAccess.Repositories;

namespace SatelliteProvider.Services.RouteManagement;

public class RouteService : IRouteService
{
    private readonly IRouteRepository _routeRepository;
    private readonly IRegionService _regionService;
    private readonly ILogger<RouteService> _logger;
    private readonly RouteValidator _validator;
    private readonly RoutePointGraphBuilder _pointGraphBuilder;
    private readonly GeofenceGridCalculator _geofenceGridCalculator;
    private readonly RouteResponseMapper _responseMapper;

    public RouteService(
        IRouteRepository routeRepository,
        IRegionService regionService,
        IOptions<ProcessingConfig> processingConfig,
        ILogger<RouteService> logger)
        : this(routeRepository, regionService, logger,
              new RouteValidator(processingConfig),
              new RoutePointGraphBuilder(processingConfig),
              new GeofenceGridCalculator(),
              new RouteResponseMapper())
    {
    }

    public RouteService(
        IRouteRepository routeRepository,
        IRegionService regionService,
        ILogger<RouteService> logger,
        RouteValidator validator,
        RoutePointGraphBuilder pointGraphBuilder,
        GeofenceGridCalculator geofenceGridCalculator,
        RouteResponseMapper responseMapper)
    {
        _routeRepository = routeRepository;
        _regionService = regionService;
        _logger = logger;
        _validator = validator;
        _pointGraphBuilder = pointGraphBuilder;
        _geofenceGridCalculator = geofenceGridCalculator;
        _responseMapper = responseMapper;
    }

    public async Task<RouteResponse> CreateRouteAsync(CreateRouteRequest request)
    {
        ArgumentNullException.ThrowIfNull(request);

        // AZ-362: idempotent POST contract. A retried POST with the same caller-supplied
        // Id returns the existing route instead of re-running point generation and
        // re-queueing geofence regions.
        var existing = await GetRouteAsync(request.Id);
        if (existing != null)
        {
            _logger.LogInformation(
                "Idempotent route POST: id {RouteId} already exists; returning existing resource",
                request.Id);
            return existing;
        }

        _validator.Validate(request);

        var graph = _pointGraphBuilder.Build(request.Points);

        var now = DateTime.UtcNow;
        var routeEntity = new RouteEntity
        {
            Id = request.Id,
            Name = request.Name,
            Description = request.Description,
            RegionSizeMeters = request.RegionSizeMeters,
            ZoomLevel = request.ZoomLevel,
            TotalDistanceMeters = graph.TotalDistanceMeters,
            TotalPoints = graph.Points.Count,
            RequestMaps = request.RequestMaps,
            CreateTilesZip = request.CreateTilesZip,
            MapsReady = false,
            CreatedAt = now,
            UpdatedAt = now,
        };

        await _routeRepository.InsertRouteAsync(routeEntity);

        var pointEntities = graph.Points.Select(p => new RoutePointEntity
        {
            Id = Guid.NewGuid(),
            RouteId = request.Id,
            SequenceNumber = p.SequenceNumber,
            Latitude = p.Latitude,
            Longitude = p.Longitude,
            PointType = p.PointType,
            SegmentIndex = p.SegmentIndex,
            DistanceFromPrevious = p.DistanceFromPrevious,
            CreatedAt = now,
        }).ToList();

        await _routeRepository.InsertRoutePointsAsync(pointEntities);

        await ProcessGeofencePolygonsAsync(request);

        return _responseMapper.Map(routeEntity, graph.Points);
    }

    public async Task<RouteResponse?> GetRouteAsync(Guid id)
    {
        var route = await _routeRepository.GetByIdAsync(id);
        if (route == null)
        {
            return null;
        }

        var points = await _routeRepository.GetRoutePointsAsync(id);
        return _responseMapper.Map(route, points);
    }

    private async Task ProcessGeofencePolygonsAsync(CreateRouteRequest request)
    {
        var polygons = request.Geofences?.Polygons;
        if (polygons is null || polygons.Count == 0)
        {
            return;
        }

        for (int polygonIndex = 0; polygonIndex < polygons.Count; polygonIndex++)
        {
            var polygon = polygons[polygonIndex];
            // Validator (above) guarantees NorthWest/SouthEast are non-null and well-formed.
            var regions = _geofenceGridCalculator.GenerateRegions(
                polygon.NorthWest!,
                polygon.SouthEast!,
                request.RegionSizeMeters);

            foreach (var center in regions)
            {
                var geofenceRegionId = Guid.NewGuid();

                await _regionService.RequestRegionAsync(
                    geofenceRegionId,
                    center.Lat,
                    center.Lon,
                    request.RegionSizeMeters,
                    request.ZoomLevel,
                    stitchTiles: false);

                await _routeRepository.LinkRouteToRegionAsync(
                    request.Id,
                    geofenceRegionId,
                    isGeofence: true,
                    geofencePolygonIndex: polygonIndex);
            }
        }
    }
}
