using Extrudex.Domain.Enums;
using Extrudex.Domain.Interfaces;
using Extrudex.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace Extrudex.Infrastructure.Configuration;

/// <summary>
/// Service that syncs filament usage data from Moonraker printers into the
/// Extrudex database. Queries all active Moonraker printers, fetches their
/// current filament usage metrics, and updates spool remaining weights and
/// print job records.
/// </summary>
public class FilamentUsageSyncService : IFilamentUsageSyncService
{
    private readonly ExtrudexDbContext _dbContext;
    private readonly IMoonrakerClient _moonrakerClient;
    private readonly ILogger<FilamentUsageSyncService> _logger;

    /// <summary>
    /// Creates a new FilamentUsageSyncService.
    /// </summary>
    /// <param name="dbContext">The EF Core database context for persisting updates.</param>
    /// <param name="moonrakerClient">The Moonraker HTTP client for fetching printer data.</param>
    /// <param name="logger">Logger for diagnostic output.</param>
    public FilamentUsageSyncService(
        ExtrudexDbContext dbContext,
        IMoonrakerClient moonrakerClient,
        ILogger<FilamentUsageSyncService> logger)
    {
        _dbContext = dbContext;
        _moonrakerClient = moonrakerClient;
        _logger = logger;
    }

    /// <inheritdoc />
    public async Task<int> SyncAllAsync(CancellationToken cancellationToken = default)
    {
        _logger.LogInformation("Starting filament usage sync cycle");

        var printers = await _dbContext.Printers
            .Where(p => p.IsActive && p.ConnectionType == ConnectionType.Moonraker)
            .Include(p => p.AmsUnits)
                .ThenInclude(u => u.Slots)
                    .ThenInclude(s => s.Spool)
            .ToListAsync(cancellationToken);

        if (printers.Count == 0)
        {
            _logger.LogInformation("No active Moonraker printers found — skipping sync");
            return 0;
        }

        _logger.LogInformation("Found {PrinterCount} active Moonraker printer(s) to sync", printers.Count);

        var syncedCount = 0;

        foreach (var printer in printers)
        {
            try
            {
                var usageData = await _moonrakerClient.GetFilamentUsageAsync(
                    printer.HostnameOrIp,
                    printer.Port,
                    printer.ApiKey,
                    cancellationToken);

                if (usageData.Count == 0)
                {
                    _logger.LogWarning(
                        "No usage data returned from printer {PrinterName} ({Host}:{Port})",
                        printer.Name, printer.HostnameOrIp, printer.Port);
                    continue;
                }

                // Update spool remaining weights from AMS data
                UpdateSpoolWeights(printer, usageData);

                // Mark printer as seen and idle (reachable = idle, not printing)
                printer.LastSeenAt = DateTime.UtcNow;
                printer.Status = PrinterStatus.Idle;

                syncedCount++;
                _logger.LogInformation(
                    "Successfully synced filament usage from printer {PrinterName}",
                    printer.Name);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex,
                    "Error syncing filament usage from printer {PrinterName} ({Host}:{Port})",
                    printer.Name, printer.HostnameOrIp, printer.Port);
            }
        }

        await _dbContext.SaveChangesAsync(cancellationToken);

        _logger.LogInformation(
            "Filament usage sync cycle complete — {SyncedCount}/{TotalCount} printers synced",
            syncedCount, printers.Count);

        return syncedCount;
    }

    /// <summary>
    /// Updates spool remaining weights based on usage data received from Moonraker.
    /// For printers with AMS units, updates the remaining weight on each slot's spool.
    /// </summary>
    private void UpdateSpoolWeights(
        Domain.Entities.Printer printer,
        Dictionary<string, decimal> usageData)
    {
        // Update AMS slot remaining weights if available
        foreach (var amsUnit in printer.AmsUnits)
        {
            foreach (var slot in amsUnit.Slots)
            {
                if (slot.Spool != null && slot.RemainingWeightG.HasValue)
                {
                    // Sync the AMS-reported remaining weight to the spool
                    slot.Spool.WeightRemainingGrams = slot.RemainingWeightG.Value;

                    _logger.LogDebug(
                        "Updated spool {SpoolSerial} remaining weight to {Weight}g",
                        slot.Spool.SpoolSerial, slot.RemainingWeightG.Value);
                }
            }
        }

        // If usage data contains extruded mm, log it for observability
        if (usageData.TryGetValue("mm_extruded", out var mmExtruded) && mmExtruded > 0)
        {
            _logger.LogInformation(
                "Printer {PrinterName} reports {MmExtruded}mm filament extruded in latest job",
                printer.Name, mmExtruded);
        }
    }
}