using System.Diagnostics.CodeAnalysis;
using System.Xml;

namespace aeqw89.tools.Publish;

// -------------------------------------------------------------------------------------------------
// Minimal replacements for VsTools.Projects so ProjectFile can keep its public surface intact
// -------------------------------------------------------------------------------------------------

// -------------------------------------------------------------------------------------------------
// Your original public surface, now backed by System.Xml
// -------------------------------------------------------------------------------------------------

internal class ProjectFile {
    public Project Project { get; private set; }
    public string Path { get; private set; }
    private PropertyGroup MainPropertyGroup { get; set; }

    private ProjectFile(Project project, string path) {
        MainPropertyGroup = project.PropertyGroups.FirstOrDefault()!;
        Project = project;
        Path = path;
    }

    public static bool TryLoad(string path, [NotNullWhen(true)] out ProjectFile? projectFile, [NotNullWhen(false)] out string? error) {
        projectFile = null;
        error = null;

        if (!path.EndsWith(".csproj")) {
            if (!Directory.Exists(path)) {
                error = Exceptions.tried_loading_non_csproj_file;
                return false;
            }

            var csproj = Directory.EnumerateFiles(path, "*.csproj", SearchOption.TopDirectoryOnly).ToArray();
            if (csproj.Length == 0) {
                error = Exceptions.no_project_in_directory;
                return false;
            }

            if (csproj.Length > 1) {
                error = Exceptions.found_multiple_csproj;
                return false;
            }

            path = csproj[0];
        }

        try {
            projectFile = new ProjectFile(Project.Load(path), path);
            return true;
        }
        catch (Exception e) {
            error = string.Format(Exceptions.generic_error, e);
            return false;
        }
    }

    public void Backup(string? backupPath = null) {
        backupPath ??= Path + ".bak";
        File.Copy(Path, backupPath, true);
    }

    public string GetDefaultBackupLocation() => Path + ".bak";

    public bool Restore(string? backupPath = null) {
        backupPath ??= GetDefaultBackupLocation();
        if (!File.Exists(backupPath)) return false;
        File.Copy(backupPath, Path, true);
        return true;
    }

    public void Save() {
        // Check for empty property or item groups
        foreach (var ig in Project.ItemGroups.ToList().Where(ig => ig.Items.Count == 0)) {
            ig.Remove();
        }
        foreach (var pg in Project.PropertyGroups.ToList().Where(pg => pg.Count == 0)) {
            pg.Remove();
        }
        
        Project.Save();
    }

    public bool TryRepair([NotNullWhen(false)] out string? error) {
        // Ensure we have a PropertyGroup with TargetFramework
        if (MainPropertyGroup?.HasProperty("TargetFramework") != true) {
            if (!Project.PropertyGroups.Any(x => x.HasProperty("TargetFramework"))) {
                error = string.Format(Exceptions.project_file_irreparable, Path, "TargetFramework");
                return false;
            }
            MainPropertyGroup = Project.PropertyGroups.First(x => x.HasProperty("TargetFramework"));
        }

        List<string> failed = [];

        void set(string key, string value, bool required = false) {
            if (!MainPropertyGroup.HasProperty(key) || string.IsNullOrEmpty(MainPropertyGroup.GetProperty(key))) {
                if (required) {
                    failed.Add(key);
                    return;
                }
                MainPropertyGroup.SetProperty(key, value);
            }
        }

        set("Version", "1.0.0");
        set("Title", System.IO.Path.GetFileNameWithoutExtension(Path));
        set("Authors", "");
        set("Company", "");
        set("Description", "");
        set("PackageProjectUrl", "", required: false);
        set("RepositoryUrl", "", required: true);
        set("PackageId", "", required: true);

        if (failed.Count > 0) {
            error = string.Format(Exceptions.project_file_irreparable, Path, string.Join(", ", failed));
            return false;
        }

        error = null;
        return true;
    }

    public List<PackageReference> GetPackageReferences() {
        return Project.ItemGroups
            .SelectMany(g => g.Items)
            .Where(i => i.ElementName == "PackageReference")
            .OfType<PackageReference>()
            .ToList();
    }

    public List<ProjectReference> GetProjectReferences() {
        return Project.ItemGroups
            .SelectMany(g => g.Items)
            .Where(i => i.ElementName == "ProjectReference")
            .OfType<ProjectReference>()
            .ToList();
    }

    public List<Content> GetPackageDependencies() {
        return Project.ItemGroups
            .SelectMany(g => g.Items)
            .Where(i => i.ElementName == "Content")
            .OfType<Content>()
            .ToList();
    }

    private void CreateOrUpdateChild(Item item, string childName, string value) {
        var child = item.Node.SelectSingleNode(childName) as XmlElement;
        if (child != null) {
            child.InnerText = value;
            return;
        }
        
        child = item.Node.OwnerDocument!.CreateElement(childName, item.Node.NamespaceURI);
        child.InnerText = value;
        item.Node.AppendChild(child);
    }

    public string GetPackageId() {
        return MainPropertyGroup.GetProperty("PackageId");
    }
    
    public void SetPrivateAssets(Item item, PrivateAssetsValue value) {
        if (value == PrivateAssetsValue.None) {
            var child = item.Node.SelectSingleNode("./PrivateAssets") as XmlElement;
            if (child != null)
                item.Node.RemoveChild(child);
            return;
        }
        
        CreateOrUpdateChild(item, "PrivateAssets", value.ToString().ToLowerInvariant());
    }

    public void SetTransitive(Item item, bool value) 
        => CreateOrUpdateChild(item, "Transitive", value.ToString().ToLowerInvariant());

    public bool IsTransitive(Item item) {
        var transitive = item.Node.SelectSingleNode("./Transitive") as XmlElement;
        return transitive?.InnerText == "true";
    }

    public string GetAbsoluteIncludePath(Item item) {
        return System.IO.Path.GetFullPath(item.Include);
    }

    public Item AddPackage(Item otherPackage) {
        var allItems = Project.ItemGroups.SelectMany(x => x.Items);
        var existing = allItems.FirstOrDefault(x =>
            x.ElementName == "PackageReference" && x.Include == otherPackage.Include);
        if (existing != null) return existing;

        var itemGroup = Project.ItemGroups.First();
        // Import the original XmlElement to preserve Version (attr or child) and any metadata
        itemGroup.Add(otherPackage);
        return itemGroup.Items.Last();
    }


    public bool RemovePackage(Item otherPackage) {
        var grp = Project.ItemGroups
            .FirstOrDefault(g => g.Items.Any(y => y.ElementName == "PackageReference" && y.Include == otherPackage.Include));

        var pkg = grp?.Items.FirstOrDefault(x => x.ElementName == otherPackage.ElementName && x.Include == otherPackage.Include);
        if (pkg == null) return false;

        pkg.Node.ParentNode!.RemoveChild(pkg.Node);
        grp!.Items.Remove(pkg);
        return true;
    }

    public string GetVersion() => MainPropertyGroup.GetProperty("Version");

    public void SetVersion(string version) => MainPropertyGroup.SetProperty("Version", version);
}
