using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json;
using StellaOps.Vexer.Core;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

namespace StellaOps.Vexer.Policy;

public enum VexPolicyDocumentFormat
{
    Json,
    Yaml,
}

public sealed record VexPolicyBindingResult(
    bool Success,
    VexPolicyOptions? Options,
    VexConsensusPolicyOptions? NormalizedOptions,
    ImmutableArray<VexPolicyIssue> Issues);

public static class VexPolicyBinder
{
    public static VexPolicyBindingResult Bind(string content, VexPolicyDocumentFormat format)
    {
        if (string.IsNullOrWhiteSpace(content))
        {
            return Failure("policy.empty", "Policy document is empty.");
        }

        try
        {
            var options = Parse(content, format);
            return Normalize(options);
        }
        catch (JsonException ex)
        {
            return Failure("policy.parse.json", $"Failed to parse JSON policy document: {ex.Message}");
        }
        catch (YamlDotNet.Core.YamlException ex)
        {
            return Failure("policy.parse.yaml", $"Failed to parse YAML policy document: {ex.Message}");
        }
    }

    public static VexPolicyBindingResult Bind(Stream stream, VexPolicyDocumentFormat format, Encoding? encoding = null)
    {
        if (stream is null)
        {
            throw new ArgumentNullException(nameof(stream));
        }

        encoding ??= Encoding.UTF8;
        using var reader = new StreamReader(stream, encoding, detectEncodingFromByteOrderMarks: true, leaveOpen: true);
        var content = reader.ReadToEnd();
        return Bind(content, format);
    }

    private static VexPolicyBindingResult Normalize(VexPolicyOptions options)
    {
        var normalization = VexPolicyProcessing.Normalize(options);
        var hasErrors = normalization.Issues.Any(static issue => issue.Severity == VexPolicyIssueSeverity.Error);
        return new VexPolicyBindingResult(!hasErrors, options, normalization.ConsensusOptions, normalization.Issues);
    }

    private static VexPolicyBindingResult Failure(string code, string message)
    {
        var issue = new VexPolicyIssue(code, message, VexPolicyIssueSeverity.Error);
        return new VexPolicyBindingResult(false, null, null, ImmutableArray.Create(issue));
    }

    private static VexPolicyOptions Parse(string content, VexPolicyDocumentFormat format)
    {
        return format switch
        {
            VexPolicyDocumentFormat.Json => JsonSerializer.Deserialize<VexPolicyOptions>(content, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true,
                ReadCommentHandling = JsonCommentHandling.Skip,
                AllowTrailingCommas = true,
            }) ?? new VexPolicyOptions(),
            VexPolicyDocumentFormat.Yaml => BuildYamlDeserializer().Deserialize<VexPolicyOptions>(content) ?? new VexPolicyOptions(),
            _ => throw new ArgumentOutOfRangeException(nameof(format), format, "Unsupported policy document format."),
        };
    }

    private static IDeserializer BuildYamlDeserializer()
        => new DeserializerBuilder()
            .WithNamingConvention(CamelCaseNamingConvention.Instance)
            .IgnoreUnmatchedProperties()
            .Build();
}
