using System.Reflection;
using System.Text;
using System.Text.Json;
using Journal.Core.Dtos;
using Journal.Core.Services.Ai;

namespace Journal.AI;

public sealed class LlamaSharpCoachService(LlamaSharpAiService ai) : ICoachService
{
    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true
    };

    private readonly LlamaSharpAiService _ai = ai;
    private readonly string _coachRules = LoadEmbeddedResource("Coach-Rules.txt");
    private readonly string _dailyTemplate = LoadEmbeddedResource("Daily-Check-In.txt");
    private readonly string _eveningTemplate = LoadEmbeddedResource("Evening-Review.txt");
    private readonly string _weeklyTemplate = LoadEmbeddedResource("Weekly-Review.txt");

    public async Task<CoachPlanDto> DailyCheckInAsync(CoachContextDto context,
        CancellationToken cancellationToken = default)
    {
        var prefs = context.Preferences ?? new CoachPreferencesDto();
        var prompt = InterpolateTemplate(_dailyTemplate, context, prefs);
        return await RunCoachPromptAsync(prompt, "daily_checkin", cancellationToken);
    }

    public async Task<CoachPlanDto> EveningReviewAsync(CoachContextDto context,
        CancellationToken cancellationToken = default)
    {
        var prefs = context.Preferences ?? new CoachPreferencesDto();
        var prompt = InterpolateTemplate(_eveningTemplate, context, prefs);
        return await RunCoachPromptAsync(prompt, "evening_review", cancellationToken);
    }

    public async Task<CoachPlanDto> WeeklyReviewAsync(CoachContextDto context,
        CancellationToken cancellationToken = default)
    {
        var prefs = context.Preferences ?? new CoachPreferencesDto();
        var prompt = InterpolateTemplate(_weeklyTemplate, context, prefs);
        return await RunCoachPromptAsync(prompt, "weekly_review", cancellationToken);
    }

    private async Task<CoachPlanDto> RunCoachPromptAsync(string prompt, string fallbackKind,
        CancellationToken cancellationToken)
    {
        var raw = await _ai.ChatJsonAsync(prompt, cancellationToken);
        var json = ExtractJson(raw);
        if (json is not null)
        {
            try
            {
                var parsed = JsonSerializer.Deserialize<CoachPlanDto>(json, JsonOptions);
                if (parsed is not null)
                    return parsed with { Kind = fallbackKind };
            }
            catch (JsonException)
            {

            }
        }

        return new CoachPlanDto(
            Kind: fallbackKind,
            Title: "Coach Response",
            Summary: raw,
            Questions: [],
            SuggestedNextActions: [],
            SuggestedTags: [],
            Evidence: []);
    }

    private string InterpolateTemplate(string template, CoachContextDto context, CoachPreferencesDto prefs)
    {
        var contextJson = JsonSerializer.Serialize(new
        {
            recentEntries = context.RecentEntries ?? [],
            recentFragments = context.RecentFragments ?? []
        });

        var preferencesJson = JsonSerializer.Serialize(new
        {
            prefs.MaxQuestions,
            prefs.MaxNextActions
        });

        var result = template
            .Replace("{{CoachRules}}", _coachRules)
            .Replace("{{dateLocal}}", context.DateLocal)
            .Replace("{{weekStartLocal}}", context.WeekStartLocal ?? "")
            .Replace("{{weekEndLocal}}", context.WeekEndLocal ?? "")
            .Replace("{{contextJson}}", contextJson)
            .Replace("{{preferencesJson}}", preferencesJson)
            .Replace("{{maxQuestions}}", prefs.MaxQuestions.ToString())
            .Replace("{{maxNextActions}}", prefs.MaxNextActions.ToString());

        return result;
    }

    private static string? ExtractJson(string text)
    {
        var codeBlockStart = text.IndexOf("```json", StringComparison.Ordinal);
        if (codeBlockStart >= 0)
        {
            var jsonStart = text.IndexOf('{', codeBlockStart);
            var codeBlockEnd = text.IndexOf("```", codeBlockStart + 7, StringComparison.Ordinal);
            if (jsonStart >= 0 && codeBlockEnd > jsonStart)
            {
                var candidate = text[jsonStart..codeBlockEnd].Trim();
                if (TryValidateJson(candidate))
                    return candidate;
            }
        }

        var kindMarker = text.IndexOf("{\"kind\"", StringComparison.Ordinal);
        if (kindMarker < 0)
            kindMarker = text.IndexOf("{ \"kind\"", StringComparison.Ordinal);
        if (kindMarker >= 0)
        {
            var lastBrace = text.LastIndexOf('}');
            if (lastBrace > kindMarker)
            {
                var candidate = text[kindMarker..(lastBrace + 1)];
                if (TryValidateJson(candidate))
                    return candidate;
            }
        }

        var searchFrom = 0;
        var globalLastBrace = text.LastIndexOf('}');
        while (searchFrom < text.Length && globalLastBrace > searchFrom)
        {
            var bracePos = text.IndexOf('{', searchFrom);
            if (bracePos < 0 || bracePos >= globalLastBrace)
                break;

            var candidate = text[bracePos..(globalLastBrace + 1)];
            if (TryValidateJson(candidate))
                return candidate;

            searchFrom = bracePos + 1;
        }

        var firstBrace = text.IndexOf('{');
        if (firstBrace >= 0)
        {
            var repaired = TryRepairJson(text[firstBrace..]);
            if (repaired is not null)
                return repaired;
        }

        return null;
    }

    private static string? TryRepairJson(string text)
    {
        var trimmed = text.TrimEnd();
        var lastUseful = trimmed.Length - 1;
        while (lastUseful >= 0)
        {
            var ch = trimmed[lastUseful];
            if (ch is '}' or ']' or '"' or ',' or ':' or '{' or '[' || char.IsDigit(ch)
                || ch is 't' or 'r' or 'u' or 'e' or 'f' or 'a' or 'l' or 's' or 'n')
                break;
            lastUseful--;
        }

        if (lastUseful < 0) return null;
        trimmed = trimmed[..(lastUseful + 1)];

        if (trimmed.EndsWith(','))
            trimmed = trimmed[..^1];

        var openBraces = 0;
        var openBrackets = 0;
        var inString = false;
        var escape = false;

        foreach (var ch in trimmed)
        {
            if (escape) { escape = false; continue; }
            if (ch == '\\' && inString) { escape = true; continue; }
            if (ch == '"') { inString = !inString; continue; }
            if (inString) continue;

            switch (ch)
            {
                case '{': openBraces++; break;
                case '}': openBraces--; break;
                case '[': openBrackets++; break;
                case ']': openBrackets--; break;
            }
        }

        if (openBraces <= 0 && openBrackets <= 0) return null;

        var sb = new StringBuilder(trimmed);
        for (var i = 0; i < openBrackets; i++) sb.Append(']');
        for (var i = 0; i < openBraces; i++) sb.Append('}');

        var repaired = sb.ToString();
        return TryValidateJson(repaired) ? repaired : null;
    }

    private static bool TryValidateJson(string json)
    {
        try
        {
            using var doc = JsonDocument.Parse(json);
            return doc.RootElement.ValueKind == JsonValueKind.Object;
        }
        catch
        {
            return false;
        }
    }

    private static string LoadEmbeddedResource(string fileName)
    {
        var assembly = Assembly.GetExecutingAssembly();
        var resourceName = assembly.GetManifestResourceNames()
            .FirstOrDefault(n => n.EndsWith(fileName, StringComparison.OrdinalIgnoreCase)) ?? throw new FileNotFoundException($"Embedded resource not found: {fileName}");
        using var stream = assembly.GetManifestResourceStream(resourceName)!;
        using var reader = new StreamReader(stream, Encoding.UTF8);
        return reader.ReadToEnd().Trim();
    }
}
