using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Solar.Api.Constants;
using Solar.Api.Entities;
using Solar.Api.Models;

namespace Solar.Api.Services;

public class SolarService
{
    private readonly DatabaseContext databaseContext;
    private readonly IMemoryCache memoryCache;
    private readonly ILogger<SolarService> logger;

    public SolarService(
        DatabaseContext databaseContext,
        IMemoryCache memoryCache,
        ILogger<SolarService> logger)
    {
        this.databaseContext = databaseContext;
        this.memoryCache = memoryCache;

        this.logger = logger;
    }

    public async Task<DayResponse> GetDayDetails(DateOnly date)
    {
        var zeverRecords = await databaseContext
            .ZeverLogs
            .Where(zl => zl.Date == date)
            .OrderBy(zl => zl.TimeUtc)
            .ToArrayAsync();
        var envoyRecords = await databaseContext
            .EnvoyLogs
            .Where(er => er.Date == date)
            .OrderBy(er => er.TimeUtc)
            .ToArrayAsync();

        NormalizeEnvoyLogs(envoyRecords);

        return new DayResponse(
            zeverRecords
                .Select(zr => new ZeverDayLog(
                    zr.TimeUtc,
                    (int)zr.CurrentWatts,
                    (int)zr.TotalWatts))
                .ToArray(),
            envoyRecords
                .Select(er => new EnvoyDayLog(
                    er.TimeUtc,
                    (int)er.CurrentWatts,
                    (int)er.TotalWatts))
                .ToArray());
    }

    private static void NormalizeEnvoyLogs(EnvoyLog[] entities)
    {
        if (entities.Length < 1)
        {
            return;
        }

        var baseValue = entities[0].TotalWatts;
        foreach (var entity in entities)
        {
            entity.TotalWatts -= baseValue;
        }
    }

    public async Task<DaysResponse> GetDaySummaries(DateOnly start, DateOnly stop)
    {
        var zeverRecords = await databaseContext
            .ZeverSummaries
            .Where(zl => zl.Date >= start && zl.Date <= stop)
            .ToArrayAsync();

        List<DaySummaryLog> logs = new();
        var current = start;
        do
        {
            var zeverResult = zeverRecords.FirstOrDefault(zr => zr.Date == current)?.TotalWatts ?? 0;
            var envoyResult = await GetEnvoyDayTotalWatts(current);

            logs.Add(new DaySummaryLog(
                current,
                (int)zeverResult,
                envoyResult));

            current = current.AddDays(1);
        } while (current <= stop);

        return new DaysResponse(logs.ToArray());
    }

    private async Task<int> GetEnvoyDayTotalWatts(DateOnly date)
    {
        var min = await databaseContext.EnvoyLogs
            .Where(el => el.Date == date)
            .OrderBy(el => el.TotalWatts)
            .FirstOrDefaultAsync();
        var max = await databaseContext.EnvoyLogs
            .Where(el => el.Date == date)
            .OrderByDescending(el => el.TotalWatts)
            .FirstOrDefaultAsync();

        if (min == null || max == null)
        {
            return 0;
        }

        return (int)(max.TotalWatts - min.TotalWatts);
    }

    public async Task<MonthSummariesResponse> GetMonthSummaries(int fromYear, int fromMonth, int toYear, int toMonth)
    {
        var results = new List<MonthLog>();
        var current = (Year: fromYear, Month: fromMonth);
        do
        {
            var zeverResult = await GetOrAddToCache("zever", current.Year, current.Month, GetZeverMonthTotalWatts);
            var envoyYear = await GetOrAddToCache("envoy", current.Year, current.Month, GetEnvoyMonthTotalWatts);
            results.Add(new MonthLog(current.Year, current.Month, zeverResult, envoyYear));

            if (current.Month == 12)
            {
                current = (current.Year + 1, 1);
            }
            else
            {
                current = (current.Year, current.Month + 1);
            }
        } while (current.Year < toYear || current.Month < toMonth);
        return new MonthSummariesResponse(results.ToArray());
    }

    private async Task<int> GetZeverMonthTotalWatts(int year, int month)
    {
        return (int)await databaseContext.ZeverSummaries
            .Where(zs => zs.Date.Year == year && zs.Date.Month == month)
            .SumAsync(zs => zs.TotalWatts);
    }

    private async Task<int> GetEnvoyMonthTotalWatts(int year, int month)
    {
        var min = await databaseContext.EnvoyLogs
            .Where(el => el.Date.Year == year && el.Date.Month == month)
            .OrderBy(el => el.TotalWatts)
            .FirstOrDefaultAsync();
        var max = await databaseContext.EnvoyLogs
            .Where(el => el.Date.Year == year && el.Date.Month == month)
            .OrderByDescending(el => el.TotalWatts)
            .FirstOrDefaultAsync();

        if (min == null || max == null)
        {
            return 0;
        }

        return (int)(max.TotalWatts - min.TotalWatts);
    }

    private async Task<int> GetOrAddToCache(
        string source,
        int year,
        int month,
        Func<int, int, Task<int>> fetchMethod)
    {
        return await memoryCache.GetOrCreateAsync(
            CacheKeys.GetMonthSummaryCacheKey(source, year, month),
            async (cacheEntry) =>
            {
                if (DateTime.Today.Month == month && DateTime.Today.Year == year)
                {
                    cacheEntry.SlidingExpiration = TimeSpan.FromHours(12);
                }
                return await fetchMethod(year, month);
            });
    }
}