using System.Text.Json;
using ClaudeDo.Data.Models;
using Microsoft.EntityFrameworkCore;

namespace ClaudeDo.Data.Repositories;

public sealed class AppSettingsRepository
{
    private readonly ClaudeDoDbContext _context;

    public AppSettingsRepository(ClaudeDoDbContext context) => _context = context;

    public async Task<AppSettingsEntity> GetAsync(CancellationToken ct = default)
    {
        var row = await _context.AppSettings.AsNoTracking()
            .FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
        if (row is null)
        {
            row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId, ModelPresets = ModelPresets.SerializeDefaults() };
            _context.AppSettings.Add(row);
            try
            {
                await _context.SaveChangesAsync(ct);
                _context.Entry(row).State = EntityState.Detached;
            }
            catch (DbUpdateException)
            {
                // Concurrent process already inserted the singleton — discard our attempt and re-read.
                _context.Entry(row).State = EntityState.Detached;
                row = await _context.AppSettings.AsNoTracking()
                    .FirstAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
            }
            return row;
        }

        // First read after upgrading from a null model_presets column: persist the shipping
        // defaults so the Settings UI shows real, editable rows instead of a code-only fallback.
        if (row.ModelPresets is null)
            row.ModelPresets = await BackfillModelPresetsAsync(ct);

        return row;
    }

    private async Task<string> BackfillModelPresetsAsync(CancellationToken ct)
    {
        var defaults = ModelPresets.SerializeDefaults();
        var tracked = await GetOrCreateTrackedRowAsync(ct);
        if (tracked.ModelPresets is null)
        {
            tracked.ModelPresets = defaults;
            await _context.SaveChangesAsync(ct);
        }
        return defaults;
    }

    private async Task<AppSettingsEntity> GetOrCreateTrackedRowAsync(CancellationToken ct)
    {
        var row = await _context.AppSettings
            .FirstOrDefaultAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
        if (row is null)
        {
            row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId };
            _context.AppSettings.Add(row);
        }
        return row;
    }

    public async Task UpdateAsync(AppSettingsEntity updated, CancellationToken ct = default)
    {
        var row = await GetOrCreateTrackedRowAsync(ct);

        row.DefaultClaudeInstructions = updated.DefaultClaudeInstructions ?? string.Empty;
        row.DefaultModel = string.IsNullOrWhiteSpace(updated.DefaultModel) ? "sonnet" : updated.DefaultModel;
        row.DefaultMaxTurns = updated.DefaultMaxTurns;
        row.MaxTurnsCeiling = updated.MaxTurnsCeiling < 1 ? 1 : updated.MaxTurnsCeiling;
        row.DefaultPermissionMode = string.IsNullOrWhiteSpace(updated.DefaultPermissionMode)
            ? "auto" : updated.DefaultPermissionMode;
        row.MaxParallelExecutions = updated.MaxParallelExecutions < 1 ? 1 : updated.MaxParallelExecutions;
        row.WorktreeStrategy = string.IsNullOrWhiteSpace(updated.WorktreeStrategy) ? "sibling" : updated.WorktreeStrategy;
        row.CentralWorktreeRoot = string.IsNullOrWhiteSpace(updated.CentralWorktreeRoot)
            ? null : updated.CentralWorktreeRoot;
        row.WorktreeAutoCleanupEnabled = updated.WorktreeAutoCleanupEnabled;
        row.WorktreeAutoCleanupDays = updated.WorktreeAutoCleanupDays;
        row.ReportExcludedPaths = string.IsNullOrWhiteSpace(updated.ReportExcludedPaths)
            ? null : updated.ReportExcludedPaths;
        row.StandupWeekday = updated.StandupWeekday;
        row.DailyPrepMaxTasks = updated.DailyPrepMaxTasks < 1 ? 1 : updated.DailyPrepMaxTasks;
        row.SessionSkills = string.IsNullOrWhiteSpace(updated.SessionSkills) ? null : updated.SessionSkills;
        row.UsageGateFiveHourPct = Math.Clamp(updated.UsageGateFiveHourPct, 0, 100);
        row.UsageGateSevenDayPct = Math.Clamp(updated.UsageGateSevenDayPct, 0, 100);
        row.UsageThrottleFiveHourSoftPct = Math.Clamp(updated.UsageThrottleFiveHourSoftPct, 0, 100);
        row.UsageThrottleFiveHourHardPct = Math.Clamp(updated.UsageThrottleFiveHourHardPct, 0, 100);
        row.UsageThrottleSevenDaySoftPct = Math.Clamp(updated.UsageThrottleSevenDaySoftPct, 0, 100);
        row.UsageThrottleSevenDayHardPct = Math.Clamp(updated.UsageThrottleSevenDayHardPct, 0, 100);
        row.AutoContinueOnUsageLimit = updated.AutoContinueOnUsageLimit;

        await _context.SaveChangesAsync(ct);
    }

    public async Task<List<string>> GetRepoImportFoldersAsync(CancellationToken ct = default)
    {
        var json = await _context.AppSettings.AsNoTracking()
            .Where(s => s.Id == AppSettingsEntity.SingletonId)
            .Select(s => s.RepoImportFolders)
            .FirstOrDefaultAsync(ct);

        if (string.IsNullOrWhiteSpace(json)) return new List<string>();
        try { return JsonSerializer.Deserialize<List<string>>(json) ?? new List<string>(); }
        catch (JsonException) { return new List<string>(); }
    }

    public async Task SetRepoImportFoldersAsync(IEnumerable<string> folders, CancellationToken ct = default)
    {
        var list = folders.ToList();
        var row = await GetOrCreateTrackedRowAsync(ct);

        row.RepoImportFolders = list.Count == 0 ? null : JsonSerializer.Serialize(list);
        await _context.SaveChangesAsync(ct);
    }
}
