using Microsoft.EntityFrameworkCore;
using timetracker.Shared;

namespace timetracker.Data;

public class TimetrackerService(IDbContextFactory<TimetrackerDbContext> factory) : ITimetrackerService
{
    public async Task<List<WorkDay>> GetWeekAsync(int userId, DateOnly monday)
    {
        await using var db = await factory.CreateDbContextAsync();
        return await db.WorkDays
            .Include(w => w.Breaks)
            .Where(w => w.UserId == userId && w.Date >= monday && w.Date < monday.AddDays(7))
            .OrderBy(w => w.Date)
            .ToListAsync();
    }

    public async Task UpsertWorkDayAsync(WorkDay workDay)
    {
        await using var db = await factory.CreateDbContextAsync();
        var existing = await db.WorkDays
            .Include(w => w.Breaks)
            .FirstOrDefaultAsync(w => w.UserId == workDay.UserId && w.Date == workDay.Date);

        if (existing == null)
        {
            workDay.Id = 0;
            foreach (var b in workDay.Breaks) b.Id = 0;
            db.WorkDays.Add(workDay);
        }
        else
        {
            existing.StartTime = workDay.StartTime;
            existing.EndTime = workDay.EndTime;
            db.BreakEntries.RemoveRange(existing.Breaks);
            existing.Breaks.Clear();
            foreach (var b in workDay.Breaks)
                existing.Breaks.Add(new BreakEntry
                {
                    WorkDayId = existing.Id,
                    StartTime = b.StartTime,
                    EndTime = b.EndTime
                });
        }

        await db.SaveChangesAsync();
    }

    public async Task<AppSettings> GetSettingsAsync(int userId)
    {
        await using var db = await factory.CreateDbContextAsync();
        return await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == userId)
            ?? new AppSettings { UserId = userId };
    }

    public async Task SaveSettingsAsync(AppSettings settings)
    {
        await using var db = await factory.CreateDbContextAsync();
        var existing = await db.AppSettings.FirstOrDefaultAsync(s => s.UserId == settings.UserId);
        if (existing == null)
            db.AppSettings.Add(settings);
        else
        {
            existing.DailyTargetHours = settings.DailyTargetHours;
            existing.MinimumBreakMinutes = settings.MinimumBreakMinutes;
            existing.VacationDaysPerYear = settings.VacationDaysPerYear;
            existing.WorkMonday = settings.WorkMonday;
            existing.WorkTuesday = settings.WorkTuesday;
            existing.WorkWednesday = settings.WorkWednesday;
            existing.WorkThursday = settings.WorkThursday;
            existing.WorkFriday = settings.WorkFriday;
            existing.WorkSaturday = settings.WorkSaturday;
            existing.WorkSunday = settings.WorkSunday;
        }
        await db.SaveChangesAsync();
    }

    // ── Urlaub ────────────────────────────────────────────────────────────
    public async Task<List<VacationDay>> GetVacationDaysAsync(int userId, int year)
    {
        await using var db = await factory.CreateDbContextAsync();
        return await db.VacationDays
            .Where(v => v.UserId == userId && v.Date.Year == year)
            .OrderBy(v => v.Date)
            .ToListAsync();
    }

    public async Task AddVacationDayAsync(VacationDay vacationDay)
    {
        await using var db = await factory.CreateDbContextAsync();
        var exists = await db.VacationDays.AnyAsync(v => v.UserId == vacationDay.UserId && v.Date == vacationDay.Date);
        if (!exists)
        {
            vacationDay.Id = 0;
            db.VacationDays.Add(vacationDay);
            await db.SaveChangesAsync();
        }
    }

    public async Task RemoveVacationDayAsync(int userId, int id)
    {
        await using var db = await factory.CreateDbContextAsync();
        var v = await db.VacationDays.FirstOrDefaultAsync(v => v.Id == id && v.UserId == userId);
        if (v != null)
        {
            db.VacationDays.Remove(v);
            await db.SaveChangesAsync();
        }
    }

    // ── Gleitzeitkonto ───────────────────────────────────────────────────
    public async Task<TimeSpan> GetTotalOvertimeAsync(int userId, AppSettings settings)
    {
        await using var db = await factory.CreateDbContextAsync();

        // 1. Finde das Startdatum für die Berechnung
        var firstDay = await db.WorkDays
            .Where(w => w.UserId == userId)
            .OrderBy(w => w.Date)
            .Select(w => (DateOnly?)w.Date)
            .FirstOrDefaultAsync();

        if (firstDay == null && !settings.FlexTimeStartDate.HasValue)
            return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);

        var startDate = settings.FlexTimeStartDate ?? firstDay!.Value;
        var endDate = DateOnly.FromDateTime(DateTime.Today);

        if (startDate > endDate)
            return TimeSpan.FromHours(settings.FlexTimeStartingBalanceHours);

        // 2. Lade alle erfassten Arbeitstage in diesem Zeitraum
        var workDays = await db.WorkDays
            .Include(w => w.Breaks)
            .Where(w => w.UserId == userId && w.Date >= startDate && w.Date <= endDate)
            .ToDictionaryAsync(w => w.Date);

        // 3. Lade alle Feiertage in diesem Zeitraum
        var holidaysList = await db.PublicHolidays
            .Where(h => h.Date >= startDate && h.Date <= endDate)
            .ToListAsync();

        var state = settings.GermanState;
        var holidaySet = holidaysList
            .Where(h => string.IsNullOrEmpty(h.Counties) || (!string.IsNullOrEmpty(state) && h.Counties.Split(',').Contains(state)))
            .Select(h => h.Date)
            .ToHashSet();

        // 4. Lade alle Urlaubstage in diesem Zeitraum
        var vacationSet = await db.VacationDays
            .Where(v => v.UserId == userId && v.Date >= startDate && v.Date <= endDate)
            .Select(v => v.Date)
            .ToHashSetAsync();

        double totalOvertimeHours = settings.FlexTimeStartingBalanceHours;

        for (var date = startDate; date <= endDate; date = date.AddDays(1))
        {
            bool isWorkDay = settings.IsWorkDay(date.DayOfWeek);
            bool isHoliday = holidaySet.Contains(date);
            bool isVacation = vacationSet.Contains(date);

            // Sollzeit gilt nur an regulären Arbeitstagen, die weder Feiertag noch Urlaub sind
            double target = (isWorkDay && !isHoliday && !isVacation) ? settings.DailyTargetHours : 0.0;
            double actual = 0.0;

            if (workDays.TryGetValue(date, out var wd) && wd.StartTime != null && wd.EndTime != null)
            {
                var gross = wd.EndTime.Value.ToTimeSpan() - wd.StartTime.Value.ToTimeSpan();
                if (gross > TimeSpan.Zero)
                {
                    var breakTotal = wd.Breaks
                        .Where(b => b.StartTime.HasValue && b.EndTime.HasValue && b.EndTime > b.StartTime)
                        .Aggregate(TimeSpan.Zero, (s, b) =>
                            s + (b.EndTime!.Value.ToTimeSpan() - b.StartTime!.Value.ToTimeSpan()));
                    var minMinutes = gross.TotalHours >= 9.0 ? 45 : 30;
                    var minBreak = TimeSpan.FromMinutes(Math.Max(minMinutes, settings.MinimumBreakMinutes));
                    var finalBreak = breakTotal > minBreak ? breakTotal : minBreak;
                    actual = (gross - finalBreak).TotalHours;
                    if (actual < 0) actual = 0.0;
                }
            }

            totalOvertimeHours += (actual - target);
        }

        return TimeSpan.FromHours(totalOvertimeHours);
    }

    // ── Monatsübersicht ───────────────────────────────────────────────────
    public async Task<List<WorkDay>> GetMonthAsync(int userId, int year, int month)
    {
        await using var db = await factory.CreateDbContextAsync();
        var from = new DateOnly(year, month, 1);
        var to = from.AddMonths(1);
        return await db.WorkDays
            .Include(w => w.Breaks)
            .Where(w => w.UserId == userId && w.Date >= from && w.Date < to)
            .OrderBy(w => w.Date)
            .ToListAsync();
    }
}
