using System.Text.Json;
using ClaudeDo.Data;
using ClaudeDo.Data.Repositories;
using ClaudeDo.Worker.Report.Interfaces;
using ClaudeDo.Worker.Runner;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;

namespace ClaudeDo.Worker.Report;

public sealed class WeekReportService
{
    private static readonly string[] DefaultExcludes = { @"C:\Private" };
    private const string NoActivity = "_No activity in this period._";

    private readonly IClaudeHistoryReader _reader;
    private readonly IDbContextFactory<ClaudeDoDbContext> _dbFactory;
    private readonly IClaudeProcess _claude;
    private readonly ILogger<WeekReportService> _logger;

    public WeekReportService(
        IClaudeHistoryReader reader,
        IDbContextFactory<ClaudeDoDbContext> dbFactory,
        IClaudeProcess claude,
        ILogger<WeekReportService> logger)
    {
        _reader = reader;
        _dbFactory = dbFactory;
        _claude = claude;
        _logger = logger;
    }

    public async Task<string?> GetStoredAsync(DateOnly start, DateOnly end, CancellationToken ct = default)
    {
        await using var ctx = await _dbFactory.CreateDbContextAsync(ct);
        var row = await new WeekReportRepository(ctx).GetByRangeAsync(start, end, ct);
        return row?.Markdown;
    }

    public async Task<string> GenerateAsync(DateOnly start, DateOnly end, CancellationToken ct = default)
    {
        string[] excludes;
        string model;
        IReadOnlyList<Data.Models.DailyNoteEntity> noteRows;

        await using (var ctx = await _dbFactory.CreateDbContextAsync(ct))
        {
            var settings = await new AppSettingsRepository(ctx).GetAsync(ct);
            excludes = ParseExcludes(settings.ReportExcludedPaths);
            model = string.IsNullOrWhiteSpace(settings.DefaultModel) ? "sonnet" : settings.DefaultModel;
            noteRows = await new DailyNoteRepository(ctx).ListBetweenAsync(start, end, ct);
        }

        var activity = await _reader.ReadAsync(start, end, excludes, ct);
        var notesByDay = noteRows
            .GroupBy(n => n.Date)
            .ToDictionary(g => g.Key, g => g.Select(n => n.Text).ToList());

        string markdown;
        var hasActivity = activity.Any(r => r.Days.Any(d => d.Prompts.Count > 0 || d.Summaries.Count > 0));
        if (!hasActivity && notesByDay.Count == 0)
        {
            markdown = NoActivity;
        }
        else
        {
            var prompt = WeekReportPromptBuilder.Build(start, end, activity, notesByDay);
            // Guard against argument injection via the model setting: model aliases/ids are
            // alphanumerics, dashes and dots only.
            var safeModel = new string(model.Where(c => char.IsLetterOrDigit(c) || c is '-' or '.').ToArray());
            if (safeModel.Length == 0) safeModel = "sonnet";
            IReadOnlyList<string> args =
            [
                "-p", "--output-format", "stream-json", "--verbose",
                "--permission-mode", "auto",
                "--model", safeModel,
            ];
            var result = await _claude.RunAsync(args, prompt, Path.GetTempPath(), _ => Task.CompletedTask, ct);
            if (!result.IsSuccess)
                throw new InvalidOperationException(result.ErrorMarkdown ?? "Claude could not generate the report.");
            markdown = result.ResultMarkdown!;
        }

        await using (var ctx = await _dbFactory.CreateDbContextAsync(ct))
            await new WeekReportRepository(ctx).UpsertAsync(start, end, markdown, ct);

        return markdown;
    }

    private static string[] ParseExcludes(string? json)
    {
        if (string.IsNullOrWhiteSpace(json)) return DefaultExcludes;
        try
        {
            var list = JsonSerializer.Deserialize<List<string>>(json);
            return list is { Count: > 0 } ? list.ToArray() : DefaultExcludes;
        }
        catch (JsonException) { return DefaultExcludes; }
    }
}
