using System.Text.Json;
using eSPJ.Models;

namespace eSPJ.Services
{
    public class FileDetailPenjemputanStore : IDetailPenjemputanStore
    {
        private readonly string _storeFilePath;
        private readonly ILogger<FileDetailPenjemputanStore> _logger;
        private static readonly SemaphoreSlim _fileLock = new SemaphoreSlim(1, 1);

        public FileDetailPenjemputanStore(
            IWebHostEnvironment env,
            ILogger<FileDetailPenjemputanStore> logger)
        {
            _logger = logger;
            _storeFilePath = Path.Combine(env.ContentRootPath, "Data", "detail-penjemputan.json");
        }

        public async Task<List<TpsData>> GetAllAsync()
        {
            await _fileLock.WaitAsync();
            try 
            {
                return await ReadAllFromDiskAsync();
            }
            finally
            {
                _fileLock.Release();
            }
        }

        private async Task<List<TpsData>> ReadAllFromDiskAsync()
        {
            try
            {
                if (!File.Exists(_storeFilePath))
                {
                    return new List<TpsData>();
                }

                var json = await File.ReadAllTextAsync(_storeFilePath);
                if (string.IsNullOrWhiteSpace(json))
                {
                    return new List<TpsData>();
                }

                var data = JsonSerializer.Deserialize<List<TpsData>>(json, new JsonSerializerOptions
                {
                    PropertyNameCaseInsensitive = true
                });

                return data?.Where(item => item != null).ToList() ?? new List<TpsData>();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Error reading submitted penjemputan data");
                return new List<TpsData>();
            }
        }

        public async Task<List<TpsData>> GetByNomorSpjAsync(string nomorSpj)
        {
            var normalizedNomorSpj = (nomorSpj ?? string.Empty).Trim();
            if (string.IsNullOrWhiteSpace(normalizedNomorSpj))
            {
                return new List<TpsData>();
            }

            var allData = await GetAllAsync();
            return allData
                .Where(item => string.Equals((item.NomorSpj ?? string.Empty).Trim(), normalizedNomorSpj, StringComparison.OrdinalIgnoreCase))
                .OrderByDescending(item => item.UpdatedAt)
                .GroupBy(GetRecordIdentity, StringComparer.OrdinalIgnoreCase)
                .Select(group => group.First())
                .OrderBy(item => item.Name)
                .ToList();
        }

        public async Task<List<TpsData>> GetSubmittedAsync()
        {
            var allData = await GetAllAsync();
            return allData
                .Where(item => item.IsSubmit)
                .OrderByDescending(item => item.UpdatedAt)
                .GroupBy(GetRecordIdentity, StringComparer.OrdinalIgnoreCase)
                .Select(group => group.First())
                .ToList();
        }

        private static string GetRecordIdentity(TpsData item)
        {
            return string.Join("::", new[]
            {
                item.NomorSpj?.Trim() ?? string.Empty,
                item.SpjDetailId?.Trim() ?? string.Empty,
                item.LokasiAngkutId?.Trim() ?? string.Empty,
                item.Name?.Trim() ?? string.Empty,
            });
        }

        public async Task<TpsData?> GetSubmittedDetailAsync(string nomorSpj, string? spjDetailId = null, string? lokasiAngkutId = null, string? namaTps = null)
        {
            var normalizedNomorSpj = (nomorSpj ?? string.Empty).Trim();
            var normalizedSpjDetailId = (spjDetailId ?? string.Empty).Trim();
            var normalizedLokasiAngkutId = (lokasiAngkutId ?? string.Empty).Trim();
            var normalizedNamaTps = (namaTps ?? string.Empty).Trim();
            var allData = await GetSubmittedAsync();
            return allData.FirstOrDefault(item =>
                string.Equals((item.NomorSpj ?? string.Empty).Trim(), normalizedNomorSpj, StringComparison.OrdinalIgnoreCase) &&
                (string.IsNullOrWhiteSpace(normalizedSpjDetailId) || string.Equals((item.SpjDetailId ?? string.Empty).Trim(), normalizedSpjDetailId, StringComparison.OrdinalIgnoreCase)) &&
                (string.IsNullOrWhiteSpace(normalizedLokasiAngkutId) || string.Equals((item.LokasiAngkutId ?? string.Empty).Trim(), normalizedLokasiAngkutId, StringComparison.OrdinalIgnoreCase)) &&
                (string.IsNullOrWhiteSpace(normalizedNamaTps) || string.Equals((item.Name ?? string.Empty).Trim(), normalizedNamaTps, StringComparison.OrdinalIgnoreCase)));
        }

        public async Task SaveSubmittedAsync(TpsData data)
        {
            await _fileLock.WaitAsync();
            try
            {
                var allData = await ReadAllFromDiskAsync();
                data.IsSubmit = true;
                data.UpdatedAt = DateTime.Now;
                if (!data.SubmittedAt.HasValue)
                {
                    data.SubmittedAt = DateTime.Now;
                }

                var existingIndex = FindRecordIndex(allData, data.NomorSpj, data.SpjDetailId, data.LokasiAngkutId, data.Name);

                if (existingIndex >= 0)
                {
                    allData[existingIndex] = data;
                }
                else
                {
                    allData.Add(data);
                }

                await WriteAllToDiskAsync(allData);
            }
            finally
            {
                _fileLock.Release();
            }
        }

        public async Task SaveRecordAsync(RecordSaveRequest request)
        {
            await _fileLock.WaitAsync();
            try 
            {
                var allData = await ReadAllFromDiskAsync();
            var mapped = MapRequestToRecord(request);
                mapped.UpdatedAt = DateTime.Now;

                var existingIndex = FindRecordIndex(allData, mapped.NomorSpj, mapped.SpjDetailId, mapped.LokasiAngkutId, mapped.Name);
                if (existingIndex >= 0)
                {
                    var existing = allData[existingIndex];
                    mapped.NomorSpj = string.IsNullOrWhiteSpace(mapped.NomorSpj) ? existing.NomorSpj : mapped.NomorSpj;
                    mapped.LokasiAngkutId = string.IsNullOrWhiteSpace(mapped.LokasiAngkutId) ? existing.LokasiAngkutId : mapped.LokasiAngkutId;
                    mapped.SpjDetailId = string.IsNullOrWhiteSpace(mapped.SpjDetailId) ? existing.SpjDetailId : mapped.SpjDetailId;
                    mapped.Name = string.IsNullOrWhiteSpace(mapped.Name) ? existing.Name : mapped.Name;
                    mapped.Latitude = string.IsNullOrWhiteSpace(mapped.Latitude) ? existing.Latitude : mapped.Latitude;
                    mapped.Longitude = string.IsNullOrWhiteSpace(mapped.Longitude) ? existing.Longitude : mapped.Longitude;
                    mapped.AlamatJalan = string.IsNullOrWhiteSpace(mapped.AlamatJalan) ? existing.AlamatJalan : mapped.AlamatJalan;
                    mapped.WaktuKedatangan = string.IsNullOrWhiteSpace(mapped.WaktuKedatangan) ? existing.WaktuKedatangan : mapped.WaktuKedatangan;
                    mapped.FotoKedatangan = mapped.FotoKedatangan.Count > 0 ? mapped.FotoKedatangan : (existing.FotoKedatangan ?? new List<string>());
                    mapped.FotoKedatanganUploaded = mapped.FotoKedatanganUploaded || existing.FotoKedatanganUploaded;
                    mapped.Timbangan = MergeTimbangan(existing.Timbangan, mapped.Timbangan);
                    mapped.TotalOrganik = mapped.TotalOrganik != 0 ? mapped.TotalOrganik : existing.TotalOrganik;
                    mapped.TotalAnorganik = mapped.TotalAnorganik != 0 ? mapped.TotalAnorganik : existing.TotalAnorganik;
                    mapped.TotalResidu = mapped.TotalResidu != 0 ? mapped.TotalResidu : existing.TotalResidu;
                    mapped.TotalTimbangan = mapped.TotalTimbangan != 0 ? mapped.TotalTimbangan : existing.TotalTimbangan;
                    mapped.FotoPetugas = mapped.FotoPetugas.Count > 0 ? mapped.FotoPetugas : (existing.FotoPetugas ?? new List<string>());
                    mapped.FotoPetugasUploaded = mapped.FotoPetugasUploaded || existing.FotoPetugasUploaded;
                    mapped.NamaPetugas = string.IsNullOrWhiteSpace(mapped.NamaPetugas) ? existing.NamaPetugas : mapped.NamaPetugas;
                    mapped.IsSubmit = existing.IsSubmit || mapped.IsSubmit;
                    mapped.SubmittedAt = existing.SubmittedAt;
                    allData[existingIndex] = mapped;
                }
                else
                {
                    allData.Add(mapped);
                }

                await WriteAllToDiskAsync(allData);
            }
            finally
            {
                _fileLock.Release();
            }
        }

        private static List<TimbanganItem> MergeTimbangan(List<TimbanganItem>? existingItems, List<TimbanganItem>? incomingItems)
        {
            var existing = existingItems ?? new List<TimbanganItem>();
            var incoming = incomingItems ?? new List<TimbanganItem>();

            if (incoming.Count == 0)
            {
                return existing;
            }

            var maxCount = Math.Max(existing.Count, incoming.Count);
            var merged = new List<TimbanganItem>(maxCount);

            for (var index = 0; index < maxCount; index++)
            {
                var existingItem = index < existing.Count ? existing[index] : null;
                var incomingItem = index < incoming.Count ? incoming[index] : null;

                if (existingItem == null && incomingItem == null)
                {
                    continue;
                }

                if (existingItem == null)
                {
                    merged.Add(CloneTimbanganItem(incomingItem!));
                    continue;
                }

                if (incomingItem == null)
                {
                    merged.Add(CloneTimbanganItem(existingItem));
                    continue;
                }

                merged.Add(new TimbanganItem
                {
                    FotoFileName = string.IsNullOrWhiteSpace(incomingItem.FotoFileName)
                        ? existingItem.FotoFileName
                        : incomingItem.FotoFileName,
                    Berat = incomingItem.Berat != null && incomingItem.Berat.Count > 0
                        ? new List<decimal>(incomingItem.Berat)
                        : new List<decimal>(existingItem.Berat ?? new List<decimal>()),
                    LokasiAngkut = incomingItem.LokasiAngkut != null && incomingItem.LokasiAngkut.Count > 0
                        ? new List<string>(incomingItem.LokasiAngkut)
                        : new List<string>(existingItem.LokasiAngkut ?? new List<string>()),
                    JenisSampah = incomingItem.JenisSampah != null && incomingItem.JenisSampah.Count > 0
                        ? new List<JenisSampah>(incomingItem.JenisSampah)
                        : new List<JenisSampah>(existingItem.JenisSampah ?? new List<JenisSampah>()),
                    IsUploaded = existingItem.IsUploaded || incomingItem.IsUploaded,
                    WaktuUpload = incomingItem.WaktuUpload ?? existingItem.WaktuUpload,
                });
            }

            return merged;
        }

        private static TimbanganItem CloneTimbanganItem(TimbanganItem source)
        {
            return new TimbanganItem
            {
                FotoFileName = source.FotoFileName,
                Berat = new List<decimal>(source.Berat ?? new List<decimal>()),
                LokasiAngkut = new List<string>(source.LokasiAngkut ?? new List<string>()),
                JenisSampah = new List<JenisSampah>(source.JenisSampah ?? new List<JenisSampah>()),
                IsUploaded = source.IsUploaded,
                WaktuUpload = source.WaktuUpload,
            };
        }

        private async Task WriteAllToDiskAsync(List<TpsData> data)
        {
            var directory = Path.GetDirectoryName(_storeFilePath);
            if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
            {
                Directory.CreateDirectory(directory);
            }

            data = data
                .Where(item => item != null)
                .OrderByDescending(item => item.UpdatedAt)
                .GroupBy(GetRecordIdentity, StringComparer.OrdinalIgnoreCase)
                .Select(group => group.First())
                .OrderBy(item => item.Name)
                .ToList();

            var options = new JsonSerializerOptions { WriteIndented = true };
            var json = JsonSerializer.Serialize(data, options);
            var tempPath = _storeFilePath + ".tmp";
            await File.WriteAllTextAsync(tempPath, json);
            File.Move(tempPath, _storeFilePath, overwrite: true);
        }

        private static int FindRecordIndex(List<TpsData> allData, string? nomorSpj, string? spjDetailId, string? lokasiAngkutId, string? namaTps)
        {
            return allData.FindIndex(item =>
                item != null &&
                !string.IsNullOrWhiteSpace(nomorSpj) &&
                string.Equals(item.NomorSpj, nomorSpj, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(item.SpjDetailId, spjDetailId ?? string.Empty, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(item.LokasiAngkutId, lokasiAngkutId ?? string.Empty, StringComparison.OrdinalIgnoreCase) &&
                string.Equals(item.Name, namaTps ?? string.Empty, StringComparison.OrdinalIgnoreCase));
        }

        private static TpsData MapRequestToRecord(RecordSaveRequest request)
        {
            return new TpsData
            {
                NomorSpj = request.NomorSpj,
                LokasiAngkutId = request.LokasiAngkutId,
                SpjDetailId = request.SpjDetailId,
                Name = request.NamaTps,
                Latitude = request.Latitude,
                Longitude = request.Longitude,
                AlamatJalan = request.AlamatJalan,
                WaktuKedatangan = request.WaktuKedatangan,
                FotoKedatangan = request.FotoKedatanganFileNames ?? new List<string>(),
                FotoKedatanganUploaded = request.FotoKedatanganUploaded,
                Timbangan = (request.Timbangan ?? new List<RecordTimbanganItem>())
                    .Where(item => item != null)
                    .Select(item => new TimbanganItem
                {
                    FotoFileName = item.FotoFileName,
                    Berat = new List<decimal> { item.Berat },
                    LokasiAngkut = new List<string>(),
                    JenisSampah = Enum.TryParse<JenisSampah>(item.JenisSampah, out var parsedJenis)
                        ? new List<JenisSampah> { parsedJenis }
                        : new List<JenisSampah> { JenisSampah.Residu },
                    IsUploaded = item.Uploaded,
                    WaktuUpload = DateTime.Now
                }).ToList(),
                TotalOrganik = request.TotalOrganik,
                TotalAnorganik = request.TotalAnorganik,
                TotalResidu = request.TotalResidu,
                TotalTimbangan = request.TotalTimbangan,
                FotoPetugas = request.FotoPetugasFileNames ?? new List<string>(),
                FotoPetugasUploaded = request.FotoPetugasUploaded,
                NamaPetugas = request.NamaPetugas,
                IsSubmit = request.IsSubmit,
                UpdatedAt = DateTime.Now,
                SubmittedAt = request.IsSubmit ? DateTime.Now : null,
            };
        }
    }
}
