using System.Xml.Linq;
using YMhut.Box.Core.Logging;

namespace YMhut.Box.Core.App;

public sealed record OpenSourceReferenceItem(
    string Name,
    string Kind,
    string Version,
    string Usage,
    string Attribution,
    string Path = "");

public interface IOpenSourceReferenceService
{
    Task<IReadOnlyList<OpenSourceReferenceItem>> GetReferencesAsync(CancellationToken cancellationToken = default);
}

public sealed class OpenSourceReferenceService(AppPaths paths, ILogService? logService = null) : IOpenSourceReferenceService
{
    private static readonly StringComparer PathComparer = StringComparer.OrdinalIgnoreCase;

    public async Task<IReadOnlyList<OpenSourceReferenceItem>> GetReferencesAsync(CancellationToken cancellationToken = default)
    {
        var references = new List<OpenSourceReferenceItem>
        {
            new(
                "YMhut Box",
                "Application",
                string.Empty,
                "Primary desktop toolbox application and integration shell.",
                "Copyright and branding belong to YMhut / YMhut Box."),
            new(
                "tubatool reference project",
                "Reference project attribution",
                string.Empty,
                "Toolbox layout, built-in tool behavior, settings interaction, and bundled Tools directory were used as reference material with author permission.",
                "Original reference project author attribution is retained here; migrated user-facing branding is YMhut Box."),
            new(
                ".NET",
                "Runtime",
                "net10.0",
                "Application runtime, libraries, file IO, process, JSON, XML, and platform interop.",
                "Microsoft and .NET contributors retain their original rights.")
        };

        try
        {
            AddPackageReferences(references);
            AddBundledToolNotices(references, cancellationToken);
            var toolNoticeCount = references.Count(item => item.Kind == "Third-party tool notice");
            await WriteLogAsync(
                "Information",
                "about",
                "Open source references loaded",
                $"count={references.Count}; toolNotices={toolNoticeCount}",
                cancellationToken).ConfigureAwait(false);
        }
        catch (Exception exception) when (exception is IOException or UnauthorizedAccessException or global::System.Xml.XmlException)
        {
            await WriteLogAsync(
                "Warning",
                "about",
                "Open source reference scan degraded",
                Sanitize(exception.Message),
                cancellationToken).ConfigureAwait(false);
        }

        return references
            .OrderBy(item => ReferenceOrder(item.Kind))
            .ThenBy(item => item.Name, StringComparer.CurrentCultureIgnoreCase)
            .ToArray();
    }

    private void AddPackageReferences(List<OpenSourceReferenceItem> references)
    {
        var packages = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
        foreach (var projectFile in FindProjectFiles())
        {
            var document = XDocument.Load(projectFile);
            foreach (var package in document.Descendants("PackageReference"))
            {
                var name = package.Attribute("Include")?.Value ?? package.Attribute("Update")?.Value;
                if (string.IsNullOrWhiteSpace(name))
                {
                    continue;
                }

                var version = package.Attribute("Version")?.Value
                    ?? package.Element("Version")?.Value
                    ?? string.Empty;
                packages[name] = version;
            }
        }

        if (packages.Count == 0)
        {
            foreach (var package in StaticPackageFallback())
            {
                packages[package.Name] = package.Version;
            }
        }

        foreach (var package in packages.OrderBy(item => item.Key, StringComparer.OrdinalIgnoreCase))
        {
            references.Add(new OpenSourceReferenceItem(
                package.Key,
                "NuGet package",
                package.Value,
                PackageUsage(package.Key),
                "Original package license and ownership remain with its maintainers."));
        }
    }

    private void AddBundledToolNotices(List<OpenSourceReferenceItem> references, CancellationToken cancellationToken)
    {
        var toolsRoot = FindToolsRoot();
        if (string.IsNullOrWhiteSpace(toolsRoot) || !Directory.Exists(toolsRoot))
        {
            references.Add(new OpenSourceReferenceItem(
                "Bundled third-party Tools",
                "Third-party tool notice",
                string.Empty,
                "Tools directory was not found in the current runtime layout.",
                "Third-party tool declarations are preserved when the Tools directory is present."));
            return;
        }

        var files = Directory.EnumerateFiles(toolsRoot, "*.*", SearchOption.AllDirectories)
            .Where(IsNoticeFile)
            .Take(120)
            .ToArray();

        foreach (var file in files)
        {
            cancellationToken.ThrowIfCancellationRequested();
            references.Add(new OpenSourceReferenceItem(
                ToolNoticeName(toolsRoot, file),
                "Third-party tool notice",
                string.Empty,
                "Bundled external tool notice. Open the original file for full license, EULA, README, or copyright text.",
                "Original third-party declaration file is kept unchanged on disk.",
                file));
        }
    }

    private IEnumerable<string> FindProjectFiles()
    {
        var seen = new HashSet<string>(PathComparer);
        foreach (var root in CandidateRoots())
        {
            if (!Directory.Exists(root))
            {
                continue;
            }

            foreach (var project in Directory.EnumerateFiles(root, "*.csproj", SearchOption.TopDirectoryOnly).Take(12))
            {
                if (seen.Add(project))
                {
                    yield return project;
                }
            }

            var src = Path.Combine(root, "src");
            if (!Directory.Exists(src))
            {
                continue;
            }

            foreach (var project in Directory.EnumerateFiles(src, "*.csproj", SearchOption.AllDirectories).Take(24))
            {
                if (seen.Add(project))
                {
                    yield return project;
                }
            }
        }
    }

    private string FindToolsRoot()
    {
        foreach (var root in CandidateRoots())
        {
            var direct = Path.Combine(root, "Tools");
            if (Directory.Exists(direct))
            {
                return direct;
            }

            foreach (var child in SafeEnumerateDirectories(root))
            {
                if (!Path.GetFileName(child).Contains("tubatool", StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                var tools = Path.Combine(child, "Tools");
                if (Directory.Exists(tools))
                {
                    return tools;
                }
            }
        }

        return string.Empty;
    }

    private IEnumerable<string> CandidateRoots()
    {
        foreach (var root in InstallLayoutPaths.CandidateRoots())
        {
            yield return root;
        }

        yield return paths.Root;
        yield return paths.Assets;

        foreach (var root in InstallLayoutPaths.CandidateRoots())
        {
            var directory = new DirectoryInfo(root);
            for (var depth = 0; depth < 8 && directory is not null; depth++, directory = directory.Parent)
            {
                yield return directory.FullName;
            }
        }
    }

    private static IEnumerable<string> SafeEnumerateDirectories(string root)
    {
        try
        {
            return Directory.Exists(root) ? Directory.EnumerateDirectories(root).Take(40).ToArray() : [];
        }
        catch
        {
            return [];
        }
    }

    private static bool IsNoticeFile(string path)
    {
        var name = Path.GetFileName(path);
        return name.Contains("license", StringComparison.OrdinalIgnoreCase)
            || name.Contains("readme", StringComparison.OrdinalIgnoreCase)
            || name.Contains("eula", StringComparison.OrdinalIgnoreCase)
            || name.Contains("copyright", StringComparison.OrdinalIgnoreCase)
            || name.Contains("copying", StringComparison.OrdinalIgnoreCase)
            || name.Contains("notice", StringComparison.OrdinalIgnoreCase);
    }

    private static string ToolNoticeName(string toolsRoot, string file)
    {
        var relative = Path.GetRelativePath(toolsRoot, file);
        var parts = relative.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
        if (parts.Length >= 3)
        {
            return $"{parts[1]} / {parts[^1]}";
        }

        return relative;
    }

    private static IEnumerable<(string Name, string Version)> StaticPackageFallback()
    {
        yield return ("Microsoft.Data.Sqlite", "10.0.0");
        yield return ("Microsoft.Extensions.DependencyInjection", "10.0.0");
        yield return ("Microsoft.Web.WebView2", "1.0.3967.48");
        yield return ("Microsoft.WindowsAppSDK", "1.8.260416003");
        yield return ("QRCoder", "1.8.0");
        yield return ("System.Drawing.Common", "10.0.0");
        yield return ("System.Management", "10.0.0");
        yield return ("ZXing.Net", "0.16.11");
    }

    private static string PackageUsage(string packageName)
    {
        return packageName switch
        {
            "Microsoft.WindowsAppSDK" => "WinUI 3 desktop shell, windows, controls, app lifecycle, and packaging support.",
            "Microsoft.Web.WebView2" => "Embedded web content for browser, plugin, and preview surfaces.",
            "Microsoft.Extensions.DependencyInjection" => "Application service registration and dependency injection.",
            "Microsoft.Data.Sqlite" => "Local SQLite logging and app data persistence.",
            "QRCoder" => "QR code generation tools.",
            "ZXing.Net" => "Barcode and QR decoding helpers.",
            "System.Drawing.Common" => "EXE and shortcut icon extraction and image conversion for external Tools.",
            "System.Management" => "WMI hardware and system information queries.",
            _ => "Application dependency used by YMhut Box."
        };
    }

    private static int ReferenceOrder(string kind)
    {
        return kind switch
        {
            "Application" => 0,
            "Reference project attribution" => 1,
            "Runtime" => 2,
            "NuGet package" => 3,
            _ => 4
        };
    }

    private static string Sanitize(string value)
    {
        return value
            .Replace(Environment.UserName, "<user>", StringComparison.OrdinalIgnoreCase)
            .Replace(AppContext.BaseDirectory, "<app>", StringComparison.OrdinalIgnoreCase);
    }

    private Task WriteLogAsync(string level, string category, string message, string? detail, CancellationToken cancellationToken)
    {
        return logService?.WriteAsync(level, category, message, detail, cancellationToken) ?? Task.CompletedTask;
    }
}
