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

namespace SatelliteProvider.Services.RouteManagement;

// AZ-364 / C11: thin orchestrator after the god-class decomposition.
// Polls the route repository, classifies region statuses, queues region work
// via IRegionService (AZ-360 / C08 — direct dependency, no IServiceProvider),
// and dispatches output generation to the per-concern collaborators
// (RouteCsvWriter, RouteSummaryWriter, RouteImageRenderer, TilesZipBuilder,
// RegionFileCleaner, RouteRegionMatcher).
public class RouteProcessingService : BackgroundService
{
    private readonly IRouteRepository _routeRepository;
    private readonly IRegionRepository _regionRepository;
    private readonly IRegionService _regionService;
    private readonly RouteCsvWriter _routeCsvWriter;
    private readonly RouteSummaryWriter _routeSummaryWriter;
    private readonly RouteImageRenderer _routeImageRenderer;
    private readonly TilesZipBuilder _tilesZipBuilder;
    private readonly RegionFileCleaner _regionFileCleaner;
    private readonly RouteRegionMatcher _routeRegionMatcher;
    private readonly ILogger<RouteProcessingService> _logger;
    private readonly TimeSpan _checkInterval;

    public RouteProcessingService(
        IRouteRepository routeRepository,
        IRegionRepository regionRepository,
        IRegionService regionService,
        RouteCsvWriter routeCsvWriter,
        RouteSummaryWriter routeSummaryWriter,
        RouteImageRenderer routeImageRenderer,
        TilesZipBuilder tilesZipBuilder,
        RegionFileCleaner regionFileCleaner,
        IOptions<ProcessingConfig> processingConfig,
        ILogger<RouteProcessingService> logger)
    {
        _routeRepository = routeRepository;
        _regionRepository = regionRepository;
        _regionService = regionService;
        _routeCsvWriter = routeCsvWriter;
        _routeSummaryWriter = routeSummaryWriter;
        _routeImageRenderer = routeImageRenderer;
        _tilesZipBuilder = tilesZipBuilder;
        _regionFileCleaner = regionFileCleaner;
        _routeRegionMatcher = new RouteRegionMatcher();
        _checkInterval = TimeSpan.FromSeconds(processingConfig.Value.RouteProcessingPollIntervalSeconds);
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("Route Processing Service started");

        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                await ProcessPendingRoutesAsync(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error in route processing service");
            }

            await Task.Delay(_checkInterval, stoppingToken);
        }

        _logger.LogInformation("Route Processing Service stopped");
    }

    private async Task ProcessPendingRoutesAsync(CancellationToken cancellationToken)
    {
        var pendingRoutes = await _routeRepository.GetRoutesWithPendingMapsAsync();

        foreach (var route in pendingRoutes)
        {
            if (cancellationToken.IsCancellationRequested)
                break;

            try
            {
                await ProcessRouteSequentiallyAsync(route.Id, cancellationToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error processing route {RouteId}", route.Id);
            }
        }
    }

    private async Task ProcessRouteSequentiallyAsync(Guid routeId, CancellationToken cancellationToken)
    {
        var route = await _routeRepository.GetByIdAsync(routeId);
        if (route == null)
        {
            _logger.LogWarning("Route {RouteId}: Route not found, skipping processing", routeId);
            return;
        }

        if (!route.RequestMaps || route.MapsReady)
        {
            return;
        }

        var routePointsList = (await _routeRepository.GetRoutePointsAsync(routeId)).ToList();
        var regionIdsList = (await _routeRepository.GetRegionIdsByRouteAsync(routeId)).ToList();
        var geofenceRegionIdsList = (await _routeRepository.GetGeofenceRegionIdsByRouteAsync(routeId)).ToList();

        var allRegionIds = regionIdsList.Union(geofenceRegionIdsList).ToList();

        if (regionIdsList.Count == 0 && routePointsList.Count > 0)
        {
            foreach (var point in routePointsList)
            {
                var regionId = Guid.NewGuid();

                await _regionService.RequestRegionAsync(
                    regionId,
                    point.Latitude,
                    point.Longitude,
                    route.RegionSizeMeters,
                    route.ZoomLevel,
                    stitchTiles: false);

                await _routeRepository.LinkRouteToRegionAsync(routeId, regionId);
            }

            return;
        }

        var regions = new List<RegionEntity>();
        foreach (var regionId in allRegionIds)
        {
            var region = await _regionRepository.GetByIdAsync(regionId);
            if (region != null)
            {
                regions.Add(region);
            }
        }

        var completedRegions = regions.Where(r => r.Status == RegionStatus.Completed).ToList();
        var failedRegions = regions.Where(r => r.Status == RegionStatus.Failed).ToList();
        var processingRegions = regions.Where(r => r.Status == RegionStatus.Queued || r.Status == RegionStatus.Processing).ToList();

        var completedRoutePointRegions = completedRegions.Where(r => !geofenceRegionIdsList.Contains(r.Id)).ToList();
        var completedGeofenceRegions = completedRegions.Where(r => geofenceRegionIdsList.Contains(r.Id)).ToList();

        var hasRoutePointRegions = regionIdsList.Count > 0;
        var hasEnoughRoutePointRegions = !hasRoutePointRegions || completedRoutePointRegions.Count >= routePointsList.Count;
        var hasAllGeofenceRegions = geofenceRegionIdsList.Count == 0 || completedGeofenceRegions.Count >= geofenceRegionIdsList.Count;
        var hasEnoughCompleted = hasEnoughRoutePointRegions && hasAllGeofenceRegions;

        var activeRegions = completedRegions.Count + processingRegions.Count;
        var shouldRetryFailed = failedRegions.Count > 0 && !hasEnoughCompleted && activeRegions < allRegionIds.Count;

        if (hasEnoughCompleted)
        {
            var orderedRouteRegions = _routeRegionMatcher.Match(routePointsList, completedRoutePointRegions);
            var regionsForOutput = orderedRouteRegions.Concat(completedGeofenceRegions)
                .GroupBy(r => r.Id)
                .Select(g => g.First())
                .ToList();

            await GenerateRouteMapsAsync(routeId, route, regionsForOutput, completedGeofenceRegions, routePointsList, cancellationToken);
            return;
        }

        if (shouldRetryFailed)
        {
            foreach (var failedRegion in failedRegions)
            {
                var newRegionId = Guid.NewGuid();

                await _regionService.RequestRegionAsync(
                    newRegionId,
                    failedRegion.Latitude,
                    failedRegion.Longitude,
                    failedRegion.SizeMeters,
                    failedRegion.ZoomLevel,
                    stitchTiles: false);

                await _routeRepository.LinkRouteToRegionAsync(routeId, newRegionId);
            }

            return;
        }

        var anyProcessing = processingRegions.Count > 0;
        if (anyProcessing)
        {
            return;
        }

        _logger.LogWarning(
            "Route {RouteId}: No processing regions, but not enough completed. This is an unexpected state - hasEnoughCompleted={HasEnough}, shouldRetryFailed={ShouldRetry}, anyProcessing={AnyProcessing}",
            routeId, hasEnoughCompleted, shouldRetryFailed, anyProcessing);
    }

    private async Task GenerateRouteMapsAsync(
        Guid routeId,
        RouteEntity route,
        IReadOnlyList<RegionEntity> regionsForOutput,
        IReadOnlyList<RegionEntity> geofenceRegions,
        IReadOnlyList<RoutePointEntity> routePoints,
        CancellationToken cancellationToken)
    {
        try
        {
            var allTiles = new Dictionary<string, TileInfo>();
            int totalTilesFromRegions = 0;
            int duplicateTiles = 0;

            foreach (var region in regionsForOutput)
            {
                if (string.IsNullOrEmpty(region.CsvFilePath) || !File.Exists(region.CsvFilePath))
                {
                    _logger.LogWarning("Route {RouteId}: Region {RegionId} CSV not found", routeId, region.Id);
                    continue;
                }

                var csvLines = await File.ReadAllLinesAsync(region.CsvFilePath, cancellationToken);

                foreach (var line in csvLines.Skip(1))
                {
                    var parts = line.Split(',');
                    if (parts.Length < 3) continue;

                    if (!double.TryParse(parts[0], out var lat))
                    {
                        continue;
                    }
                    if (!double.TryParse(parts[1], out var lon))
                    {
                        continue;
                    }
                    var filePath = parts[2];

                    totalTilesFromRegions++;
                    var key = $"{lat:F6}_{lon:F6}";

                    if (!allTiles.ContainsKey(key))
                    {
                        allTiles[key] = new TileInfo(lat, lon, filePath);
                    }
                    else
                    {
                        duplicateTiles++;
                    }
                }
            }

            var tileList = allTiles.Values.ToList();

            var csvPath = await _routeCsvWriter.WriteAsync(routeId, tileList, cancellationToken);

            string? stitchedImagePath = null;
            if (route.RequestMaps)
            {
                var geofencePolygonBounds = await ComputeGeofencePolygonBoundsAsync(routeId, route.ZoomLevel, cancellationToken);
                stitchedImagePath = await _routeImageRenderer.RenderAsync(
                    routeId,
                    tileList,
                    route.ZoomLevel,
                    geofencePolygonBounds,
                    routePoints,
                    cancellationToken);
            }

            string? tilesZipPath = null;
            if (route.CreateTilesZip)
            {
                tilesZipPath = await _tilesZipBuilder.BuildAsync(routeId, tileList, cancellationToken);
            }

            var summaryPath = await _routeSummaryWriter.WriteAsync(
                route,
                tileList.Count,
                totalTilesFromRegions,
                duplicateTiles,
                tilesZipPath,
                cancellationToken);

            route.MapsReady = true;
            route.CsvFilePath = csvPath;
            route.SummaryFilePath = summaryPath;
            route.StitchedImagePath = stitchedImagePath;
            route.TilesZipPath = tilesZipPath;
            route.UpdatedAt = DateTime.UtcNow;

            await _routeRepository.UpdateRouteAsync(route);

            await _regionFileCleaner.CleanupAsync(regionsForOutput, cancellationToken);

            _logger.LogInformation("Route {RouteId} maps processing completed successfully", routeId);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error generating maps for route {RouteId}", routeId);
            throw;
        }
    }

    private async Task<List<(int MinX, int MinY, int MaxX, int MaxY)>> ComputeGeofencePolygonBoundsAsync(
        Guid routeId,
        int zoomLevel,
        CancellationToken cancellationToken)
    {
        var bounds = new List<(int MinX, int MinY, int MaxX, int MaxY)>();
        var geofencesByPolygon = await _routeRepository.GetGeofenceRegionsByPolygonAsync(routeId);

        foreach (var (_, polygonRegionIds) in geofencesByPolygon.OrderBy(kvp => kvp.Key))
        {
            int? minX = null, minY = null, maxX = null, maxY = null;

            foreach (var geofenceId in polygonRegionIds)
            {
                var region = await _regionRepository.GetByIdAsync(geofenceId);
                if (region == null || string.IsNullOrEmpty(region.CsvFilePath) || !File.Exists(region.CsvFilePath))
                {
                    continue;
                }

                var csvLines = await File.ReadAllLinesAsync(region.CsvFilePath, cancellationToken);

                foreach (var line in csvLines.Skip(1))
                {
                    var parts = line.Split(',');
                    if (parts.Length < 3) continue;
                    if (!double.TryParse(parts[0], out var lat) || !double.TryParse(parts[1], out var lon))
                    {
                        continue;
                    }

                    var tile = GeoUtils.WorldToTilePos(new GeoPoint(lat, lon), zoomLevel);
                    minX = minX == null ? tile.x : Math.Min(minX.Value, tile.x);
                    minY = minY == null ? tile.y : Math.Min(minY.Value, tile.y);
                    maxX = maxX == null ? tile.x : Math.Max(maxX.Value, tile.x);
                    maxY = maxY == null ? tile.y : Math.Max(maxY.Value, tile.y);
                }
            }

            if (minX.HasValue && minY.HasValue && maxX.HasValue && maxY.HasValue)
            {
                bounds.Add((minX.Value, minY.Value, maxX.Value, maxY.Value));
            }
        }

        return bounds;
    }
}
