using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using StellaOps.Scanner.Core.Contracts;

namespace StellaOps.Scanner.Core.Utility;

public static class ScannerIdentifiers
{
    private static readonly Guid ScanJobNamespace = new("d985aa76-8c2b-4cba-bac0-c98c90674f04");
    private static readonly Guid CorrelationNamespace = new("7cde18f5-729e-4ea1-be3d-46fda4c55e38");

    public static ScanJobId CreateJobId(
        string imageReference,
        string? imageDigest = null,
        string? tenantId = null,
        string? salt = null)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(imageReference);

        var normalizedReference = NormalizeImageReference(imageReference);
        var normalizedDigest = NormalizeDigest(imageDigest) ?? "none";
        var normalizedTenant = string.IsNullOrWhiteSpace(tenantId) ? "global" : tenantId.Trim().ToLowerInvariant();
        var normalizedSalt = (salt?.Trim() ?? string.Empty).ToLowerInvariant();

        using var sha256 = SHA256.Create();
        var payload = $"{normalizedReference}|{normalizedDigest}|{normalizedTenant}|{normalizedSalt}";
        var hashed = sha256.ComputeHash(Encoding.UTF8.GetBytes(payload));
        return new ScanJobId(CreateGuidFromHash(ScanJobNamespace, hashed));
    }

    public static string CreateCorrelationId(ScanJobId jobId, string? stage = null, string? suffix = null)
    {
        var normalizedStage = string.IsNullOrWhiteSpace(stage)
            ? "scan"
            : stage.Trim().ToLowerInvariant().Replace(' ', '-');

        var normalizedSuffix = string.IsNullOrWhiteSpace(suffix)
            ? string.Empty
            : "-" + suffix.Trim().ToLowerInvariant().Replace(' ', '-');

        return $"scan-{normalizedStage}-{jobId}{normalizedSuffix}";
    }

    public static string CreateDeterministicHash(params string[] segments)
    {
        if (segments is null || segments.Length == 0)
        {
            throw new ArgumentException("At least one segment must be provided.", nameof(segments));
        }

        using var sha256 = SHA256.Create();
        var joined = string.Join('|', segments.Select(static s => s?.Trim() ?? string.Empty));
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(joined));
        return Convert.ToHexString(hash).ToLowerInvariant();
    }

    public static Guid CreateDeterministicGuid(Guid namespaceId, ReadOnlySpan<byte> nameBytes)
    {
        Span<byte> namespaceBytes = stackalloc byte[16];
        namespaceId.TryWriteBytes(namespaceBytes);

        Span<byte> buffer = stackalloc byte[namespaceBytes.Length + nameBytes.Length];
        namespaceBytes.CopyTo(buffer);
        nameBytes.CopyTo(buffer[namespaceBytes.Length..]);

        Span<byte> hash = stackalloc byte[32];
        SHA256.TryHashData(buffer, hash, out _);

        Span<byte> guidBytes = stackalloc byte[16];
        hash[..16].CopyTo(guidBytes);

        guidBytes[6] = (byte)((guidBytes[6] & 0x0F) | 0x50);
        guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);

        return new Guid(guidBytes);
    }

    public static string NormalizeImageReference(string reference)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(reference);
        var trimmed = reference.Trim();
        var atIndex = trimmed.IndexOf('@');
        if (atIndex > 0)
        {
            var prefix = trimmed[..atIndex].ToLowerInvariant();
            return $"{prefix}{trimmed[atIndex..]}";
        }

        var colonIndex = trimmed.IndexOf(':');
        if (colonIndex > 0)
        {
            var name = trimmed[..colonIndex].ToLowerInvariant();
            var tag = trimmed[(colonIndex + 1)..];
            return $"{name}:{tag}";
        }

        return trimmed.ToLowerInvariant();
    }

    public static string? NormalizeDigest(string? digest)
    {
        if (string.IsNullOrWhiteSpace(digest))
        {
            return null;
        }

        var trimmed = digest.Trim();
        var parts = trimmed.Split(':', 2, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
        if (parts.Length != 2)
        {
            return trimmed.ToLowerInvariant();
        }

        return $"{parts[0].ToLowerInvariant()}:{parts[1].ToLowerInvariant()}";
    }

    public static string CreateDeterministicCorrelation(string audience, ScanJobId jobId, string? component = null)
    {
        using var sha256 = SHA256.Create();
        var payload = $"{audience.Trim().ToLowerInvariant()}|{jobId}|{component?.Trim().ToLowerInvariant() ?? string.Empty}";
        var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(payload));
        var guid = CreateGuidFromHash(CorrelationNamespace, hash);
        return $"corr-{guid.ToString("n", CultureInfo.InvariantCulture)}";
    }

    private static Guid CreateGuidFromHash(Guid namespaceId, ReadOnlySpan<byte> hash)
    {
        Span<byte> guidBytes = stackalloc byte[16];
        hash[..16].CopyTo(guidBytes);
        guidBytes[6] = (byte)((guidBytes[6] & 0x0F) | 0x50);
        guidBytes[8] = (byte)((guidBytes[8] & 0x3F) | 0x80);
        return new Guid(guidBytes);
    }
}
