using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace StellaOps.Policy;

public sealed record PolicyValidationCliOptions
{
    public IReadOnlyList<string> Inputs { get; init; } = Array.Empty<string>();

    /// <summary>
    /// Writes machine-readable JSON instead of human-formatted text.
    /// </summary>
    public bool OutputJson { get; init; }

    /// <summary>
    /// When enabled, warnings cause a non-zero exit code.
    /// </summary>
    public bool Strict { get; init; }
}

public sealed record PolicyValidationFileResult(
    string Path,
    PolicyBindingResult BindingResult,
    PolicyDiagnosticsReport Diagnostics);

public sealed class PolicyValidationCli
{
    private readonly TextWriter _output;
    private readonly TextWriter _error;

    public PolicyValidationCli(TextWriter? output = null, TextWriter? error = null)
    {
        _output = output ?? Console.Out;
        _error = error ?? Console.Error;
    }

    public async Task<int> RunAsync(PolicyValidationCliOptions options, CancellationToken cancellationToken = default)
    {
        if (options is null)
        {
            throw new ArgumentNullException(nameof(options));
        }

        if (options.Inputs.Count == 0)
        {
            await _error.WriteLineAsync("No input files provided. Supply one or more policy file paths.");
            return 64; // EX_USAGE
        }

        var results = new List<PolicyValidationFileResult>();
        foreach (var input in options.Inputs)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var resolvedPaths = ResolveInput(input);
            if (resolvedPaths.Count == 0)
            {
                await _error.WriteLineAsync($"No files matched '{input}'.");
                continue;
            }

            foreach (var path in resolvedPaths)
            {
                cancellationToken.ThrowIfCancellationRequested();

                var format = PolicySchema.DetectFormat(path);
                var content = await File.ReadAllTextAsync(path, cancellationToken);
                var bindingResult = PolicyBinder.Bind(content, format);
                var diagnostics = PolicyDiagnostics.Create(bindingResult);

                results.Add(new PolicyValidationFileResult(path, bindingResult, diagnostics));
            }
        }

        if (results.Count == 0)
        {
            await _error.WriteLineAsync("No files were processed.");
            return 65; // EX_DATAERR
        }

        if (options.OutputJson)
        {
            WriteJson(results);
        }
        else
        {
            await WriteTextAsync(results, cancellationToken);
        }

        var hasErrors = results.Any(static result => !result.BindingResult.Success);
        var hasWarnings = results.Any(static result => result.BindingResult.Issues.Any(static issue => issue.Severity == PolicyIssueSeverity.Warning));

        if (hasErrors)
        {
            return 1;
        }

        if (options.Strict && hasWarnings)
        {
            return 2;
        }

        return 0;
    }

    private async Task WriteTextAsync(IReadOnlyList<PolicyValidationFileResult> results, CancellationToken cancellationToken)
    {
        foreach (var result in results)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var relativePath = MakeRelative(result.Path);
            await _output.WriteLineAsync($"{relativePath} [{result.BindingResult.Format}]");

            if (result.BindingResult.Issues.Length == 0)
            {
                await _output.WriteLineAsync("  OK");
                continue;
            }

            foreach (var issue in result.BindingResult.Issues)
            {
                var severity = issue.Severity.ToString().ToUpperInvariant().PadRight(7);
                await _output.WriteLineAsync($"  {severity} {issue.Path} :: {issue.Message} ({issue.Code})");
            }
        }
    }

    private void WriteJson(IReadOnlyList<PolicyValidationFileResult> results)
    {
        var payload = results.Select(static result => new
            {
                path = result.Path,
                format = result.BindingResult.Format.ToString().ToLowerInvariant(),
                success = result.BindingResult.Success,
                issues = result.BindingResult.Issues.Select(static issue => new
                {
                    code = issue.Code,
                    message = issue.Message,
                    severity = issue.Severity.ToString().ToLowerInvariant(),
                    path = issue.Path,
                }),
                diagnostics = new
                {
                    version = result.Diagnostics.Version,
                    ruleCount = result.Diagnostics.RuleCount,
                    errorCount = result.Diagnostics.ErrorCount,
                    warningCount = result.Diagnostics.WarningCount,
                    generatedAt = result.Diagnostics.GeneratedAt,
                    recommendations = result.Diagnostics.Recommendations,
                },
            })
            .ToArray();

        var json = JsonSerializer.Serialize(payload, new JsonSerializerOptions
        {
            WriteIndented = true,
        });
        _output.WriteLine(json);
    }

    private static IReadOnlyList<string> ResolveInput(string input)
    {
        if (string.IsNullOrWhiteSpace(input))
        {
            return Array.Empty<string>();
        }

        var expanded = Environment.ExpandEnvironmentVariables(input.Trim());
        if (File.Exists(expanded))
        {
            return new[] { Path.GetFullPath(expanded) };
        }

        if (Directory.Exists(expanded))
        {
            return Directory.EnumerateFiles(expanded, "*.*", SearchOption.TopDirectoryOnly)
                .Where(static path => MatchesPolicyExtension(path))
                .OrderBy(static path => path, StringComparer.OrdinalIgnoreCase)
                .Select(Path.GetFullPath)
                .ToArray();
        }

        var directory = Path.GetDirectoryName(expanded);
        var searchPattern = Path.GetFileName(expanded);

        if (string.IsNullOrEmpty(searchPattern))
        {
            return Array.Empty<string>();
        }

        if (string.IsNullOrEmpty(directory))
        {
            directory = ".";
        }

        if (!Directory.Exists(directory))
        {
            return Array.Empty<string>();
        }

        return Directory.EnumerateFiles(directory, searchPattern, SearchOption.TopDirectoryOnly)
            .Where(static path => MatchesPolicyExtension(path))
            .OrderBy(static path => path, StringComparer.OrdinalIgnoreCase)
            .Select(Path.GetFullPath)
            .ToArray();
    }

    private static bool MatchesPolicyExtension(string path)
    {
        var extension = Path.GetExtension(path);
        return extension.Equals(".yaml", StringComparison.OrdinalIgnoreCase)
               || extension.Equals(".yml", StringComparison.OrdinalIgnoreCase)
               || extension.Equals(".json", StringComparison.OrdinalIgnoreCase);
    }

    private static string MakeRelative(string path)
    {
        try
        {
            var fullPath = Path.GetFullPath(path);
            var current = Directory.GetCurrentDirectory();
            if (fullPath.StartsWith(current, StringComparison.OrdinalIgnoreCase))
            {
                return fullPath[current.Length..].TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
            }

            return fullPath;
        }
        catch
        {
            return path;
        }
    }
}
