using Sdt.Config;

namespace Sdt.Core;

public enum DoctorStatus
{
    Pass,
    Warn,
    Fail,
}

public sealed record DoctorCheck(
    string Name,
    DoctorStatus Status,
    string Detail,
    string? Fix = null);

public sealed record DoctorReport(
    IReadOnlyList<DoctorCheck> Checks)
{
    public bool HasFailures => Checks.Any(c => c.Status == DoctorStatus.Fail);
    public bool HasWarnings => Checks.Any(c => c.Status == DoctorStatus.Warn);
}

public sealed class ConfigDoctorService(
    IToolProbe? toolProbe = null,
    IRequirementResolver? requirementResolver = null)
{
    private readonly IToolProbe _toolProbe = toolProbe ?? new ToolProbeService();
    private readonly IRequirementResolver _requirementResolver = requirementResolver ?? new RequirementResolver();

    public async Task<DoctorReport> RunAsync(
        DevToolConfig config,
        string projectRoot,
        CancellationToken cancellationToken = default)
    {
        var checks = new List<DoctorCheck>();
        var workflowMap = config.Workflows.ToDictionary(w => w.Id, StringComparer.OrdinalIgnoreCase);

        AddSchemaChecks(config, checks);
        AddWorkflowChecks(config.Workflows, workflowMap, projectRoot, checks);
        AddPathChecks(config, projectRoot, checks);
        await AddToolProbeChecksAsync(config, projectRoot, checks, cancellationToken).ConfigureAwait(false);

        return new DoctorReport(checks);
    }

    private static void AddSchemaChecks(DevToolConfig config, List<DoctorCheck> checks)
    {
        if (config.Workflows.Count == 0 && config.Targets.Count == 0)
        {
            checks.Add(new DoctorCheck(
                "Config schema",
                DoctorStatus.Fail,
                "No workflows or legacy targets found.",
                "Add workflows or run SDT init/bootstrap."));
            return;
        }

        if (config.Workflows.Count == 0 && config.Targets.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Legacy schema",
                DoctorStatus.Fail,
                "Targets-only config detected (strict mode will block execution).",
                "Use SYSTEM -> Migrate legacy targets -> workflows."));
            return;
        }

        if (config.Targets.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Legacy schema",
                DoctorStatus.Warn,
                "Both workflows and legacy targets are present.",
                "Prefer workflows-only config and remove legacy targets once migrated."));
        }
        else
        {
            checks.Add(new DoctorCheck("Config schema", DoctorStatus.Pass, "Workflow-first config detected."));
        }
    }

    private static void AddWorkflowChecks(
        IReadOnlyList<WorkflowDefinition> workflows,
        IReadOnlyDictionary<string, WorkflowDefinition> workflowMap,
        string projectRoot,
        List<DoctorCheck> checks)
    {
        var duplicateIds = workflows
            .GroupBy(w => w.Id, StringComparer.OrdinalIgnoreCase)
            .Where(g => g.Count() > 1)
            .Select(g => g.Key)
            .ToList();

        if (duplicateIds.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Workflow IDs",
                DoctorStatus.Fail,
                $"Duplicate workflow IDs: {string.Join(", ", duplicateIds)}",
                "Ensure each workflow has a unique id."));
        }
        else
        {
            checks.Add(new DoctorCheck("Workflow IDs", DoctorStatus.Pass, "No duplicate workflow IDs."));
        }

        var brokenDeps = new List<string>();
        foreach (var workflow in workflows)
        {
            foreach (var dep in workflow.DependsOn)
            {
                if (!workflowMap.ContainsKey(dep))
                    brokenDeps.Add($"{workflow.Id} -> {dep}");
            }
        }

        if (brokenDeps.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Workflow dependencies",
                DoctorStatus.Fail,
                $"Missing dependencies: {string.Join("; ", brokenDeps)}",
                "Fix dependsOn IDs to reference existing workflows."));
        }
        else
        {
            checks.Add(new DoctorCheck("Workflow dependencies", DoctorStatus.Pass, "All workflow dependencies are valid."));
        }

        var invalidSteps = new List<string>();
        var missingWorkingDirs = new List<string>();
        foreach (var workflow in workflows)
        {
            foreach (var step in workflow.Steps)
            {
                if (string.IsNullOrWhiteSpace(step.Command) && string.IsNullOrWhiteSpace(step.Action))
                    invalidSteps.Add($"{workflow.Id}/{step.Id}");

                var stepDir = Path.GetFullPath(Path.Combine(projectRoot, step.WorkingDir));
                if (!Directory.Exists(stepDir))
                    missingWorkingDirs.Add($"{workflow.Id}/{step.Id} -> {step.WorkingDir}");
            }
        }

        if (invalidSteps.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Step definitions",
                DoctorStatus.Fail,
                $"Steps missing command/action: {string.Join(", ", invalidSteps)}",
                "Each step must define either action or command."));
        }
        else
        {
            checks.Add(new DoctorCheck("Step definitions", DoctorStatus.Pass, "All steps define an action or command."));
        }

        if (missingWorkingDirs.Count > 0)
        {
            checks.Add(new DoctorCheck(
                "Working directories",
                DoctorStatus.Warn,
                $"Missing directories: {string.Join("; ", missingWorkingDirs)}",
                "Create missing directories or fix step workingDir values."));
        }
        else
        {
            checks.Add(new DoctorCheck("Working directories", DoctorStatus.Pass, "All referenced working directories exist."));
        }
    }

    private static void AddPathChecks(DevToolConfig config, string projectRoot, List<DoctorCheck> checks)
    {
        var configPath = Path.Combine(projectRoot, "devtool.json");
        checks.Add(File.Exists(configPath)
            ? new DoctorCheck("Project root", DoctorStatus.Pass, $"Config found at {configPath}")
            : new DoctorCheck("Project root", DoctorStatus.Fail, $"devtool.json not found at {configPath}", "Run SDT init/bootstrap."));

        if (OperatingSystem.IsWindows())
        {
            var pathValue = Environment.GetEnvironmentVariable("PATH") ?? string.Empty;
            var unresolvedSegments = pathValue
                .Split(Path.PathSeparator, StringSplitOptions.RemoveEmptyEntries)
                .Where(s => s.Contains('%') && Environment.ExpandEnvironmentVariables(s) == s)
                .Take(4)
                .ToList();

            if (unresolvedSegments.Count > 0)
            {
                checks.Add(new DoctorCheck(
                    "PATH expansion",
                    DoctorStatus.Warn,
                    $"Unresolved PATH tokens: {string.Join(" | ", unresolvedSegments)}",
                    "Set referenced env vars or remove invalid PATH segments."));
            }
            else
            {
                checks.Add(new DoctorCheck("PATH expansion", DoctorStatus.Pass, "No unresolved PATH token segments detected."));
            }
        }

        if (config.Project?.RootHints.Count > 0)
        {
            checks.Add(new DoctorCheck("Root hints", DoctorStatus.Pass, $"Configured root hints: {string.Join(", ", config.Project.RootHints)}"));
        }
        else
        {
            checks.Add(new DoctorCheck(
                "Root hints",
                DoctorStatus.Warn,
                "No project.rootHints configured.",
                "Add rootHints markers (for example .git, *.sln, package.json)."));
        }
    }

    private async Task AddToolProbeChecksAsync(
        DevToolConfig config,
        string projectRoot,
        List<DoctorCheck> checks,
        CancellationToken cancellationToken)
    {
        var requiredTools = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
        foreach (var workflow in config.Workflows)
        {
            foreach (var step in workflow.Steps)
            {
                foreach (var req in _requirementResolver.Resolve(step))
                    requiredTools.Add(req.Tool);
            }
        }

        foreach (var profile in config.Debug?.Profiles ?? [])
        {
            foreach (var req in profile.Requires)
                requiredTools.Add(req.Tool);
        }

        foreach (var toolDef in config.Tooling?.Tools ?? [])
            requiredTools.Add(toolDef.Tool);

        if (requiredTools.Count == 0)
        {
            checks.Add(new DoctorCheck("Tool probes", DoctorStatus.Warn, "No tools discovered from workflows/debug/tooling."));
            return;
        }

        foreach (var tool in requiredTools.OrderBy(t => t, StringComparer.OrdinalIgnoreCase))
        {
            var probe = await _toolProbe.ProbeAsync(tool, projectRoot, config, cancellationToken).ConfigureAwait(false);
            if (probe.IsAvailable)
            {
                checks.Add(new DoctorCheck(
                    $"Tool: {tool}",
                    DoctorStatus.Pass,
                    string.IsNullOrWhiteSpace(probe.Version) ? "available" : probe.Version!,
                    probe.Details));
            }
            else
            {
                checks.Add(new DoctorCheck(
                    $"Tool: {tool}",
                    DoctorStatus.Fail,
                    string.IsNullOrWhiteSpace(probe.Details) ? "not available" : probe.Details!,
                    $"Install/configure {tool} or set tooling.tools[].executables for non-standard paths."));
            }
        }
    }
}
