using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Text.Json;

namespace TestersAI;

public static class Sinks
{
    public static List<Action<AnalysisResult>> Build(Config cfg, Action<AnalysisResult>? frameworkCallback)
    {
        var list = new List<Action<AnalysisResult>>();
        foreach (var name in (cfg.Sink ?? "").Split(','))
        {
            switch (name.Trim().ToLowerInvariant())
            {
                case "disk": list.Add(Disk(cfg.SinkDir, cfg.LogFormat)); break;
                case "return": list.Add(_ => { }); break;
                case "framework":
                    if (frameworkCallback is not null) list.Add(Safe(frameworkCallback));
                    break;
            }
        }
        return list;
    }

    public static AnalysisResult Dispatch(AnalysisResult r, List<Action<AnalysisResult>> sinks)
    {
        foreach (var s in sinks) { try { s(r); } catch { /* never bubble */ } }
        return r;
    }

    static Action<AnalysisResult> Safe(Action<AnalysisResult> cb)
        => r => { try { cb(r); } catch { } };

    static Action<AnalysisResult> Disk(string dir, string fmt) => r =>
    {
        try
        {
            Directory.CreateDirectory(dir);
            var name = $"{DateTime.UtcNow:yyyyMMddTHHmmss}-{r.Kind}-{Guid.NewGuid().ToString()[..8]}.{Ext(fmt)}";
            File.WriteAllText(Path.Combine(dir, name), Format(r, fmt));
        }
        catch { /* ignore */ }
    };

    static string Ext(string fmt) => fmt switch {
        "junit" => "xml", "tap" => "tap", "text" => "txt", _ => "json"
    };

    public static string Format(AnalysisResult r, string fmt) => fmt switch
    {
        "text" => FormatText(r),
        "tap" => FormatTap(r),
        "junit" => FormatJUnit(r),
        _ => JsonSerializer.Serialize(new {
            r.Kind, r.Ok, r.Skipped, r.Reason, r.Error, r.DurationMs, r.Issues
        }),
    };

    static string FormatText(AnalysisResult r)
    {
        var sb = new StringBuilder();
        sb.AppendLine($"[testersai:{r.Kind}] ok={r.Ok} skipped={r.Skipped} reason={r.Reason ?? "-"}");
        foreach (var i in r.Issues) sb.AppendLine($"  [{i.Severity}] {i.Category}: {i.Message}");
        return sb.ToString();
    }

    static string FormatTap(AnalysisResult r)
    {
        var sb = new StringBuilder();
        if (r.Issues.Count == 0) { sb.Append("1..1\nok 1 - ").AppendLine($"{r.Kind} clean"); return sb.ToString(); }
        sb.Append("1..").Append(r.Issues.Count).Append('\n');
        int idx = 1;
        foreach (var i in r.Issues)
        {
            var bad = i.Severity is "high" or "critical";
            sb.Append(bad ? "not ok " : "ok ").Append(idx++)
              .Append(" - [").Append(i.Severity).Append("] ")
              .Append(i.Category).Append(": ").AppendLine(i.Message);
        }
        return sb.ToString();
    }

    static string FormatJUnit(AnalysisResult r)
    {
        int failures = 0;
        foreach (var i in r.Issues) if (i.Severity is "high" or "critical") failures++;
        var cases = new StringBuilder();
        foreach (var i in r.Issues)
        {
            var bad = i.Severity is "high" or "critical";
            cases.Append("<testcase classname=\"testersai.").Append(r.Kind)
                 .Append("\" name=\"").Append(Xml($"{i.Category}:{Trunc(i.Message)}")).Append("\">");
            if (bad) cases.Append("<failure message=\"").Append(Xml(i.Message)).Append("\">")
                          .Append(Xml(i.Evidence ?? "")).Append("</failure>");
            cases.Append("</testcase>");
        }
        return $"<testsuite name=\"testersai.{r.Kind}\" tests=\"{(r.Issues.Count == 0 ? 1 : r.Issues.Count)}\" failures=\"{failures}\">{cases}</testsuite>";
    }

    static string Trunc(string s) => s.Length <= 60 ? s : s[..60];
    static string Xml(string s) => s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
                                    .Replace("\"", "&quot;").Replace("'", "&apos;");
}
