using System.Text.Json;
using System.Text.Json.Nodes;
using Sdt.Core;

namespace Sdt.Config;

public sealed record LoadedProjectConfig(
    DevToolConfig Config,
    string ProjectRoot,
    IReadOnlyList<string> Warnings);

public sealed record LegacyMigrationApplyResult(
    bool Success,
    string Message,
    string? BackupPath = null,
    string? ConfigPath = null);

public static class ConfigLoader
{
    public const string WorkspaceDefaultsFileName = "sdt-defaults.json";

    private static readonly JsonSerializerOptions JsonOptions = new()
    {
        PropertyNameCaseInsensitive = true,
        PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
        AllowTrailingCommas = true,
        ReadCommentHandling = JsonCommentHandling.Skip,
    };

    /// <summary>
    /// Walks up from <paramref name="startDir"/> (or CWD) until it finds devtool.json.
    /// Returns null if not found.
    /// </summary>
    public static string? FindConfigPath(string? startDir = null)
    {
        var dir = new DirectoryInfo(startDir ?? Directory.GetCurrentDirectory());
        while (dir is not null)
        {
            var candidate = Path.Combine(dir.FullName, "devtool.json");
            if (File.Exists(candidate))
                return candidate;
            dir = dir.Parent!;
        }
        return null;
    }

    public static LoadedProjectConfig? FindAndLoad(string? startDir = null)
    {
        var configPath = FindConfigPath(startDir);
        if (configPath is null)
            return null;

        var projectRoot = Path.GetDirectoryName(configPath)
            ?? throw new InvalidOperationException($"Could not resolve project root from {configPath}");

        try
        {
            var effectiveConfig = LoadEffectiveConfig(projectRoot, configPath, out var defaultsPath);
            var warnings = new List<string>();
            if (!string.IsNullOrWhiteSpace(defaultsPath))
                warnings.Add($"Applied workspace defaults from {defaultsPath}.");

            var legacyMode = ResolveLegacyMode();
            if (legacyMode == LegacyMode.Strict && effectiveConfig.Workflows.Count == 0 && effectiveConfig.Targets.Count > 0)
            {
                var previewPath = Path.Combine(projectRoot, "devtool.generated.workflows.json");
                try
                {
                    var previewConfig = WorkflowModelBuilder.BuildMigrationPreviewConfig(effectiveConfig, new RequirementResolver());
                    File.WriteAllText(previewPath, ConfigBootstrapper.ToJson(previewConfig));
                }
                catch
                {
                    // Keep strict failure even if preview generation fails.
                }

                throw new InvalidOperationException(
                    $"Legacy targets-only config detected at {configPath}. Strict mode requires workflows. " +
                    "Use migration preview file 'devtool.generated.workflows.json' and migrate devtool.json. " +
                    "Temporary rollback: set SDT_LEGACY_MODE=compat.");
            }

            var normalized = WorkflowModelBuilder.Normalize(effectiveConfig, legacyMode, new RequirementResolver());
            warnings.AddRange(normalized.Warnings);
            return new LoadedProjectConfig(effectiveConfig, projectRoot, warnings);
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(
                $"Failed to parse devtool.json at {configPath}: {ex.Message}", ex);
        }
    }

    public static LegacyMigrationApplyResult ApplyLegacyTargetMigration(
        string configPath,
        bool createBackup = true)
    {
        try
        {
            if (!File.Exists(configPath))
                return new LegacyMigrationApplyResult(false, $"Config file not found: {configPath}");

            var json = File.ReadAllText(configPath);
            var config = JsonSerializer.Deserialize<DevToolConfig>(json, JsonOptions)
                ?? throw new InvalidOperationException("devtool.json deserialized to null.");

            if (config.Targets.Count == 0)
                return new LegacyMigrationApplyResult(false, "No legacy targets found to migrate.", ConfigPath: configPath);

            var migrated = WorkflowModelBuilder.BuildMigrationPreviewConfig(config, new RequirementResolver());
            var backupPath = (string?)null;
            if (createBackup)
            {
                backupPath = configPath + $".bak-{DateTimeOffset.Now:yyyyMMdd-HHmmss}";
                File.Copy(configPath, backupPath, overwrite: false);
            }

            File.WriteAllText(configPath, ConfigBootstrapper.ToJson(migrated));
            return new LegacyMigrationApplyResult(
                true,
                "Legacy targets migrated to workflows.",
                BackupPath: backupPath,
                ConfigPath: configPath);
        }
        catch (Exception ex)
        {
            return new LegacyMigrationApplyResult(false, ex.Message, ConfigPath: configPath);
        }
    }

    private static LegacyMode ResolveLegacyMode()
    {
        var raw = Environment.GetEnvironmentVariable("SDT_LEGACY_MODE");
        return string.Equals(raw, "compat", StringComparison.OrdinalIgnoreCase)
            ? LegacyMode.Compat
            : LegacyMode.Strict;
    }

    private static DevToolConfig LoadEffectiveConfig(
        string projectRoot,
        string projectConfigPath,
        out string? defaultsPath)
    {
        defaultsPath = FindWorkspaceDefaultsPath(projectRoot);
        var projectObj = LoadJsonObject(projectConfigPath, "project config");
        if (string.IsNullOrWhiteSpace(defaultsPath))
            return DeserializeConfig(projectObj, projectConfigPath);

        var defaultsObj = LoadJsonObject(defaultsPath!, "workspace defaults");
        var merged = MergeObjects(defaultsObj, projectObj);
        return DeserializeConfig(merged, projectConfigPath);
    }

    private static string? FindWorkspaceDefaultsPath(string startDir)
    {
        var workspaceBoundary = FindWorkspaceBoundary(startDir);
        var dir = new DirectoryInfo(startDir);
        while (dir is not null)
        {
            var candidate = Path.Combine(dir.FullName, WorkspaceDefaultsFileName);
            if (File.Exists(candidate))
                return candidate;
            if (workspaceBoundary is not null &&
                string.Equals(dir.FullName, workspaceBoundary, StringComparison.OrdinalIgnoreCase))
            {
                break;
            }
            if (workspaceBoundary is null)
                break;
            dir = dir.Parent;
        }

        return null;
    }

    private static string? FindWorkspaceBoundary(string startDir)
    {
        var dir = new DirectoryInfo(startDir);
        while (dir is not null)
        {
            var workspacePath = Path.Combine(dir.FullName, WorkspaceLoader.FileName);
            if (File.Exists(workspacePath))
                return dir.FullName;
            dir = dir.Parent;
        }

        return null;
    }

    private static JsonObject LoadJsonObject(string path, string label)
    {
        var json = File.ReadAllText(path);
        var node = JsonNode.Parse(json)
            ?? throw new InvalidOperationException($"{label} at {path} deserialized to null.");
        if (node is not JsonObject obj)
            throw new InvalidOperationException($"{label} at {path} must be a JSON object.");
        return obj;
    }

    private static DevToolConfig DeserializeConfig(JsonObject obj, string sourcePath)
    {
        return obj.Deserialize<DevToolConfig>(JsonOptions)
            ?? throw new InvalidOperationException($"devtool.json at {sourcePath} deserialized to null.");
    }

    private static JsonObject MergeObjects(JsonObject baseObj, JsonObject overlayObj)
    {
        var result = (JsonObject)baseObj.DeepClone();
        foreach (var kv in overlayObj)
        {
            if (kv.Value is JsonObject overlayChild &&
                result[kv.Key] is JsonObject baseChild)
            {
                result[kv.Key] = MergeObjects(baseChild, overlayChild);
                continue;
            }

            result[kv.Key] = kv.Value?.DeepClone();
        }

        return result;
    }
}
