using System.Globalization; using Google.Apis.Sheets.v4; using Microsoft.Extensions.Logging; using Microsoft.EntityFrameworkCore; using DiunaBI.Core.Models; using DiunaBI.Core.Database.Context; namespace DiunaBI.Plugins.Morska.Importers; public class MorskaStandardImporter : MorskaBaseImporter { public override string ImporterType => "Morska.Import.Standard"; private readonly AppDbContext _db; private readonly SpreadsheetsResource.ValuesResource _googleSheetValues; private readonly ILogger _logger; // Configuration properties private string? SheetId { get; set; } private string? SheetTabName { get; set; } private string? DataRange { get; set; } private string? ImportYear { get; set; } private string? ImportMonth { get; set; } private string? ImportName { get; set; } private DateTime? StartDate { get; set; } private DateTime? EndDate { get; set; } private bool IsEnabled { get; set; } private IList>? _cachedSheetData; private string? _cachedDataKey; public MorskaStandardImporter( AppDbContext db, SpreadsheetsResource.ValuesResource googleSheetValues, ILogger logger) { _db = db; _googleSheetValues = googleSheetValues; _logger = logger; } public override void Import(Layer importWorker) { try { _logger.LogInformation("{ImporterType}: Starting import for {ImportWorkerName} ({ImportWorkerId})", ImporterType, importWorker.Name, importWorker.Id); // Clear cache before import _cachedSheetData = null; _cachedDataKey = null; // Load configuration from layer records LoadConfiguration(importWorker); // Check if import should be performed if (!ShouldPerformImport(importWorker)) { _logger.LogInformation("{ImporterType}: Import not needed for {ImportWorkerName}", ImporterType, importWorker.Name); return; } // Validate required configuration ValidateConfiguration(); // Perform the actual import PerformImport(importWorker); _logger.LogInformation("{ImporterType}: Successfully completed import for {ImportWorkerName}", ImporterType, importWorker.Name); } catch (Exception e) { _logger.LogError(e, "{ImporterType}: Failed to import {ImportWorkerName} ({ImportWorkerId})", ImporterType, importWorker.Name, importWorker.Id); throw; } finally { // Clear cache after import _cachedSheetData = null; _cachedDataKey = null; } } private IList>? GetSheetData() { var currentDataKey = $"{SheetId}#{SheetTabName}#{DataRange}"; if (_cachedSheetData != null && _cachedDataKey == currentDataKey) { _logger.LogDebug("{ImporterType}: Using cached sheet data for {DataKey}", ImporterType, currentDataKey); return _cachedSheetData; } try { _logger.LogDebug("{ImporterType}: Fetching data from Google Sheets API for {DataKey}", ImporterType, currentDataKey); var dataRangeResponse = _googleSheetValues.Get(SheetId!, $"{SheetTabName}!{DataRange}").Execute(); _cachedSheetData = dataRangeResponse.Values; _cachedDataKey = currentDataKey; _logger.LogDebug("{ImporterType}: Cached {RowCount} rows from Google Sheet", ImporterType, _cachedSheetData?.Count ?? 0); return _cachedSheetData; } catch (Exception e) { _logger.LogError(e, "{ImporterType}: Error fetching data from Google Sheet {SheetId}", ImporterType, SheetId); throw; } } private void LoadConfiguration(Layer importWorker) { if (importWorker.Records == null) return; SheetId = GetRecordValue(importWorker.Records, "SheetId"); SheetTabName = GetRecordValue(importWorker.Records, "SheetTabName"); DataRange = GetRecordValue(importWorker.Records, "DataRange"); ImportYear = GetRecordValue(importWorker.Records, "ImportYear"); ImportMonth = GetRecordValue(importWorker.Records, "ImportMonth"); ImportName = GetRecordValue(importWorker.Records, "ImportName"); IsEnabled = GetRecordValue(importWorker.Records, "IsEnabled") == "True"; var startDateStr = GetRecordValue(importWorker.Records, "StartDate"); if (startDateStr != null && DateTime.TryParseExact(startDateStr, "yyyy.MM.dd", null, DateTimeStyles.None, out var startDate)) { StartDate = startDate; } var endDateStr = GetRecordValue(importWorker.Records, "EndDate"); if (endDateStr != null && DateTime.TryParseExact(endDateStr, "yyyy.MM.dd", null, DateTimeStyles.None, out var endDate)) { EndDate = endDate; } _logger.LogDebug("{ImporterType}: Configuration loaded for {ImportWorkerName}", ImporterType, importWorker.Name); } private bool ShouldPerformImport(Layer importWorker) { if (!IsEnabled) { _logger.LogDebug("{ImporterType}: Import disabled for {ImportWorkerName}", ImporterType, importWorker.Name); return false; } // Check date range if configured if (StartDate.HasValue && EndDate.HasValue) { var now = DateTime.UtcNow.Date; if (now >= StartDate.Value.Date && now <= EndDate.Value.Date) { _logger.LogDebug("{ImporterType}: Within date range, import needed for {ImportWorkerName}", ImporterType, importWorker.Name); return true; } // Outside date range - check if imported layer is up to date if (!IsImportedLayerUpToDate(importWorker)) { _logger.LogDebug("{ImporterType}: Outside date range but layer is out of date, import needed for {ImportWorkerName}", ImporterType, importWorker.Name); return true; } _logger.LogDebug("{ImporterType}: Outside date range and layer is up to date for {ImportWorkerName}", ImporterType, importWorker.Name); return false; } // No date constraints - always import return true; } private void ValidateConfiguration() { var errors = new List(); if (string.IsNullOrEmpty(SheetId)) errors.Add("SheetId is required"); if (string.IsNullOrEmpty(SheetTabName)) errors.Add("SheetTabName is required"); if (string.IsNullOrEmpty(DataRange)) errors.Add("DataRange is required"); if (string.IsNullOrEmpty(ImportYear)) errors.Add("ImportYear is required"); if (string.IsNullOrEmpty(ImportMonth)) errors.Add("ImportMonth is required"); if (string.IsNullOrEmpty(ImportName)) errors.Add("ImportName is required"); if (errors.Any()) { throw new InvalidOperationException($"Configuration validation failed: {string.Join(", ", errors)}"); } } private bool IsImportedLayerUpToDate(Layer importWorker) { var newestLayer = _db.Layers .Include(x => x.Records) .Where(x => x.ParentId == importWorker.Id) .OrderByDescending(x => x.CreatedAt) .AsNoTracking() .FirstOrDefault(); if (newestLayer == null) { _logger.LogDebug("{ImporterType}: No child layers found for {ImportWorkerName}, treating as up to date", ImporterType, importWorker.Name); return true; } try { var data = GetSheetData(); if (data == null || data.Count < 2) { _logger.LogWarning("{ImporterType}: No data found in sheet for {ImportWorkerName}", ImporterType, importWorker.Name); return true; } // Check if the number of columns matches if (data[0].Count != data[1].Count) { _logger.LogWarning("{ImporterType}: Column count mismatch in imported data for {ImportWorkerName}", ImporterType, importWorker.Name); return false; } for (var i = 0; i < data[1].Count; i++) { if (string.IsNullOrEmpty(data[0][i]?.ToString()) || !double.TryParse(data[1][i]?.ToString(), CultureInfo.GetCultureInfo("pl-PL"), out var value)) { _logger.LogDebug("{ImporterType}: Skipping column {Index} - empty code or invalid value", ImporterType, i); continue; } var existingRecord = newestLayer.Records?.FirstOrDefault(x => x.Code == data[0][i].ToString()); if (existingRecord == null || existingRecord.Value1 != value) { _logger.LogDebug("{ImporterType}: Imported data is newer or different for code {Code}", ImporterType, data[0][i]); return false; } } } catch (Exception e) { _logger.LogError(e, "{ImporterType}: Error checking imported layer up-to-date status for {ImportWorkerName}", ImporterType, importWorker.Name); return false; } _logger.LogDebug("{ImporterType}: Imported layer is up to date for {ImportWorkerName}", ImporterType, importWorker.Name); return true; } private void PerformImport(Layer importWorker) { _logger.LogDebug("{ImporterType}: Importing from sheet {SheetId}, tab {SheetTabName}, range {DataRange}", ImporterType, SheetId, SheetTabName, DataRange); var layer = new Layer { Id = Guid.NewGuid(), Number = _db.Layers.Count() + 1, ParentId = importWorker.Id, Type = LayerType.Import, CreatedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"), ModifiedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"), CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }; layer.Name = $"L{layer.Number}-I-{ImportName}-{ImportYear}/{ImportMonth}-{DateTime.Now:yyyyMMddHHmm}"; var newRecords = new List(); try { var data = GetSheetData(); _logger.LogDebug("{ImporterType}: Using data with {RowCount} rows from cache", ImporterType, data?.Count ?? 0); if (data != null && data.Count >= 2) { for (var i = 0; i < data[1].Count; i++) { if (string.IsNullOrEmpty(data[0][i]?.ToString()) || !double.TryParse(data[1][i]?.ToString(), CultureInfo.GetCultureInfo("pl-PL"), out var value)) { _logger.LogDebug("{ImporterType}: Skipping column {Index} - empty code or invalid value", ImporterType, i); continue; } var record = new Record { Id = Guid.NewGuid(), Code = data[0][i].ToString(), Value1 = value, CreatedAt = DateTime.UtcNow, ModifiedAt = DateTime.UtcNow }; newRecords.Add(record); } } _db.Layers.Add(layer); SaveRecords(layer.Id, newRecords); _db.SaveChanges(); _logger.LogInformation("{ImporterType}: Successfully imported {RecordCount} records for layer {LayerName} ({LayerId})", ImporterType, newRecords.Count, layer.Name, layer.Id); } catch (Exception e) { _logger.LogError(e, "{ImporterType}: Error importing data from cached sheet data", ImporterType); throw; } } private void SaveRecords(Guid layerId, ICollection records) { var toDelete = _db.Records.Where(x => x.LayerId == layerId).ToList(); if (toDelete.Count > 0) { _db.Records.RemoveRange(toDelete); } foreach (var record in records) { record.CreatedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"); record.CreatedAt = DateTime.UtcNow; record.ModifiedById = Guid.Parse("F392209E-123E-4651-A5A4-0B1D6CF9FF9D"); record.ModifiedAt = DateTime.UtcNow; record.LayerId = layerId; _db.Records.Add(record); } _logger.LogDebug("{ImporterType}: Saved {RecordCount} records for layer {LayerId}", ImporterType, records.Count, layerId); } private string? GetRecordValue(ICollection records, string code) { return records.FirstOrDefault(x => x.Code == code)?.Desc1; } }