using System.Text.Json;

namespace GenshinAssistant.InputHelper;

internal static class IkInventoryLists
{
    public static IkInventoryListStatus Load(string dataDir)
    {
        var dir = ResolveDirectory(dataDir);
        var required = new[] { "artifacts.json", "weapons.json", "characters.json", "materials.json", "version.txt" };
        var missing = required.Where(file => !File.Exists(Path.Combine(dir, file))).ToArray();
        var status = new IkInventoryListStatus { Directory = dir, Missing = missing };
        if (missing.Length > 0) return status;

        status.Version = File.ReadAllText(Path.Combine(dir, "version.txt")).Trim();
        status.ArtifactSets = CountJsonObjectProperties(Path.Combine(dir, "artifacts.json"));
        status.ArtifactPieces = CountArtifactPieces(Path.Combine(dir, "artifacts.json"));
        status.Weapons = CountJsonObjectProperties(Path.Combine(dir, "weapons.json"));
        status.Characters = CountJsonObjectProperties(Path.Combine(dir, "characters.json"));
        status.Materials = CountJsonObjectProperties(Path.Combine(dir, "materials.json"));
        return status;
    }

    private static string ResolveDirectory(string dataDir)
    {
        if (!string.IsNullOrWhiteSpace(dataDir)) return dataDir;

        var candidates = new List<string>();
        var envDir = Environment.GetEnvironmentVariable("IK_INVENTORYLISTS_DIR");
        if (!string.IsNullOrWhiteSpace(envDir)) candidates.Add(envDir);

        AddDirectoryCandidates(candidates, AppContext.BaseDirectory);
        AddDirectoryCandidates(candidates, Environment.CurrentDirectory);

        var baseParent = Directory.GetParent(AppContext.BaseDirectory);
        for (var depth = 0; depth < 6 && baseParent != null; depth++)
        {
            AddDirectoryCandidates(candidates, baseParent.FullName);
            baseParent = baseParent.Parent;
        }

        return candidates.FirstOrDefault(IsCompleteInventoryListDirectory)
            ?? Path.Combine(AppContext.BaseDirectory, "inventorylists");
    }

    private static void AddDirectoryCandidates(List<string> candidates, string root)
    {
        if (string.IsNullOrWhiteSpace(root)) return;
        candidates.Add(Path.Combine(root, "inventorylists"));
        candidates.Add(Path.Combine(root, "ik-inventorylists"));
        candidates.Add(Path.Combine(root, "data", "ik-inventorylists"));
    }

    private static bool IsCompleteInventoryListDirectory(string dir)
    {
        return File.Exists(Path.Combine(dir, "artifacts.json"))
            && File.Exists(Path.Combine(dir, "weapons.json"))
            && File.Exists(Path.Combine(dir, "characters.json"))
            && File.Exists(Path.Combine(dir, "materials.json"))
            && File.Exists(Path.Combine(dir, "version.txt"));
    }

    public static object CatalogPayload(string dataDir)
    {
        var status = Load(dataDir);
        if (!status.Valid)
        {
            return new
            {
                data = status.ToPayload(),
                artifacts = Array.Empty<object>(),
                weapons = Array.Empty<object>(),
                characters = Array.Empty<object>(),
                materials = Array.Empty<object>(),
            };
        }

        return new
        {
            data = status.ToPayload(),
            artifacts = LoadArtifactCatalog(Path.Combine(status.Directory, "artifacts.json")),
            weapons = LoadStringMapCatalog(Path.Combine(status.Directory, "weapons.json")),
            characters = LoadCharacterCatalog(Path.Combine(status.Directory, "characters.json")),
            materials = LoadStringMapCatalog(Path.Combine(status.Directory, "materials.json")),
        };
    }

    private static int CountJsonObjectProperties(string path)
    {
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        return doc.RootElement.ValueKind == JsonValueKind.Object
            ? doc.RootElement.EnumerateObject().Count()
            : 0;
    }

    private static int CountArtifactPieces(string path)
    {
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        if (doc.RootElement.ValueKind != JsonValueKind.Object) return 0;
        var count = 0;
        foreach (var set in doc.RootElement.EnumerateObject())
        {
            if (!set.Value.TryGetProperty("artifacts", out var artifacts)) continue;
            if (artifacts.ValueKind == JsonValueKind.Object) count += artifacts.EnumerateObject().Count();
        }
        return count;
    }

    private static List<object> LoadStringMapCatalog(string path)
    {
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        var entries = new List<object>();
        if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
        foreach (var entry in doc.RootElement.EnumerateObject())
        {
            entries.Add(new
            {
                normalizedName = entry.Name,
                good = entry.Value.ValueKind == JsonValueKind.String ? entry.Value.GetString() ?? "" : "",
            });
        }
        return entries;
    }

    private static List<object> LoadCharacterCatalog(string path)
    {
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        var entries = new List<object>();
        if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
        foreach (var entry in doc.RootElement.EnumerateObject())
        {
            var value = entry.Value;
            entries.Add(new
            {
                normalizedName = entry.Name,
                good = JsonString(value, "GOOD"),
                element = JsonFirstString(value, "Element"),
                weaponType = JsonFirstInt(value, "WeaponType"),
                constellationName = JsonFirstString(value, "ConstellationName"),
            });
        }
        return entries;
    }

    private static List<object> LoadArtifactCatalog(string path)
    {
        using var doc = JsonDocument.Parse(File.ReadAllText(path));
        var entries = new List<object>();
        if (doc.RootElement.ValueKind != JsonValueKind.Object) return entries;
        foreach (var set in doc.RootElement.EnumerateObject())
        {
            var pieces = new List<object>();
            if (set.Value.TryGetProperty("artifacts", out var artifacts) && artifacts.ValueKind == JsonValueKind.Object)
            {
                foreach (var piece in artifacts.EnumerateObject())
                {
                    pieces.Add(new
                    {
                        slot = piece.Name,
                        artifactName = JsonString(piece.Value, "artifactName"),
                        good = JsonString(piece.Value, "GOOD"),
                        normalizedName = JsonString(piece.Value, "normalizedName"),
                    });
                }
            }

            entries.Add(new
            {
                normalizedName = set.Name,
                setName = JsonString(set.Value, "setName"),
                good = JsonString(set.Value, "GOOD"),
                pieces,
            });
        }
        return entries;
    }

    private static string JsonString(JsonElement value, string propertyName)
        => value.TryGetProperty(propertyName, out var property) && property.ValueKind == JsonValueKind.String
            ? property.GetString() ?? ""
            : "";

    private static string JsonFirstString(JsonElement value, string propertyName)
    {
        if (!value.TryGetProperty(propertyName, out var property)) return "";
        if (property.ValueKind == JsonValueKind.String) return property.GetString() ?? "";
        if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0)
        {
            var first = property[0];
            return first.ValueKind == JsonValueKind.String ? first.GetString() ?? "" : "";
        }
        return "";
    }

    private static int JsonFirstInt(JsonElement value, string propertyName)
    {
        if (!value.TryGetProperty(propertyName, out var property)) return -1;
        if (property.ValueKind == JsonValueKind.Number && property.TryGetInt32(out var number)) return number;
        if (property.ValueKind == JsonValueKind.Array && property.GetArrayLength() > 0)
        {
            var first = property[0];
            return first.ValueKind == JsonValueKind.Number && first.TryGetInt32(out number) ? number : -1;
        }
        return -1;
    }
}

internal sealed class IkInventoryListStatus
{
    public string Directory { get; init; } = "";
    public string Version { get; set; } = "";
    public int ArtifactSets { get; set; }
    public int ArtifactPieces { get; set; }
    public int Weapons { get; set; }
    public int Characters { get; set; }
    public int Materials { get; set; }
    public string[] Missing { get; init; } = Array.Empty<string>();
    public bool Valid => Missing.Length == 0;

    public object SupportedCategoriesPayload() => new
    {
        artifacts = ArtifactCategoryPayload("artifacts.json", ArtifactSets, ArtifactPieces),
        weapons = SimpleCategoryPayload("weapons.json", Weapons),
        characters = SimpleCategoryPayload("characters.json", Characters),
        materials = SimpleCategoryPayload("materials.json", Materials),
    };

    private object ArtifactCategoryPayload(string file, int setCount, int pieceCount)
    {
        var catalogAvailable = Valid;
        var nativeCaptureSupported = Valid;
        return new
        {
            file,
            setCount,
            pieceCount,
            supported = nativeCaptureSupported,
            catalogAvailable,
            nativeCaptureSupported,
            scanStatus = nativeCaptureSupported ? "native_capture" : "missing_data",
        };
    }

    private object SimpleCategoryPayload(string file, int count)
    {
        var catalogAvailable = Valid;
        const bool nativeCaptureSupported = false;
        return new
        {
            file,
            count,
            supported = nativeCaptureSupported,
            catalogAvailable,
            nativeCaptureSupported,
            scanStatus = catalogAvailable ? "catalog_only" : "missing_data",
        };
    }

    public object ToPayload() => new
    {
        directory = Directory,
        version = Version,
        artifactSets = ArtifactSets,
        artifactPieces = ArtifactPieces,
        weapons = Weapons,
        characters = Characters,
        materials = Materials,
        totalEntries = ArtifactPieces + Weapons + Characters + Materials,
        categories = SupportedCategoriesPayload(),
        missing = Missing,
        valid = Valid,
        source = "InventoryKamera inventorylists",
    };
}
