using EntKube.Web.Data;
using k8s;
using k8s.Models;
using Microsoft.EntityFrameworkCore;

namespace EntKube.Web.Services;

/// <summary>
/// A simple result type for Kubernetes operations. Operations can fail for
/// many reasons (no kubeconfig, cluster unreachable, pod not found), so we
/// use Result rather than exceptions for expected failures.
/// </summary>
public class KubernetesOperationResult
{
    public bool IsSuccess { get; init; }
    public string? Error { get; init; }

    public static KubernetesOperationResult Success() => new() { IsSuccess = true };
    public static KubernetesOperationResult Failure(string error) => new() { IsSuccess = false, Error = error };
}

/// <summary>
/// Result type with a data payload for operations that return information
/// (e.g. pod lists, log content).
/// </summary>
public class KubernetesOperationResult<T>
{
    public bool IsSuccess { get; init; }
    public T? Data { get; init; }
    public string? Error { get; init; }

    public static KubernetesOperationResult<T> Success(T data) => new() { IsSuccess = true, Data = data };
    public static KubernetesOperationResult<T> Failure(string error) => new() { IsSuccess = false, Error = error };
}

/// <summary>
/// A snapshot of a Kubernetes pod's state — name, status, container count,
/// restarts, age. Rendered in the portal's deployment detail view.
/// </summary>
public class PodInfo
{
    public required string Name { get; set; }
    public required string Namespace { get; set; }
    public required string Status { get; set; }
    public int ReadyContainers { get; set; }
    public int TotalContainers { get; set; }
    public int Restarts { get; set; }
    public DateTime? StartTime { get; set; }
    public List<ContainerInfo> Containers { get; set; } = [];
}

/// <summary>
/// Per-container state within a pod. Lets the user pick which container
/// to view logs for in multi-container pods.
/// </summary>
public class ContainerInfo
{
    public required string Name { get; set; }
    public required string Image { get; set; }
    public bool Ready { get; set; }
    public int RestartCount { get; set; }
    public string? State { get; set; }
}

/// <summary>
/// Performs live Kubernetes cluster operations for the customer portal —
/// listing pods, viewing logs, restarting deployments, deleting pods, and
/// scaling. Uses the stored kubeconfig from KubernetesCluster to authenticate.
///
/// Each operation:
/// 1. Loads the deployment to find which cluster it targets
/// 2. Reads the cluster's kubeconfig
/// 3. Creates a K8s client and performs the operation
/// 4. Returns a Result so the UI can show success/failure gracefully
/// </summary>
public class KubernetesOperationsService(IDbContextFactory<ApplicationDbContext> dbFactory)
{
    /// <summary>
    /// Loads a deployment with its cluster attached, so we know where
    /// to connect for Kubernetes API calls.
    /// </summary>
    public async Task<AppDeployment?> GetDeploymentWithClusterAsync(
        Guid deploymentId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();

        return await db.AppDeployments
            .Include(d => d.Cluster)
            .Include(d => d.Environment)
            .FirstOrDefaultAsync(d => d.Id == deploymentId, ct);
    }

    /// <summary>
    /// Lists all pods in the deployment's namespace, filtered by the deployment
    /// name. Gives the portal a real-time view of what's actually running.
    /// </summary>
    public async Task<KubernetesOperationResult<List<PodInfo>>> GetPodsAsync(
        Guid deploymentId, CancellationToken ct = default)
    {
        // First, load the deployment and its cluster so we know where to connect.
        AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct);

        if (deployment is null)
        {
            return KubernetesOperationResult<List<PodInfo>>.Failure("Deployment not found.");
        }

        // The cluster needs a kubeconfig to connect.
        if (string.IsNullOrEmpty(deployment.Cluster.Kubeconfig))
        {
            return KubernetesOperationResult<List<PodInfo>>.Failure(
                "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations.");
        }

        try
        {
            // Build a Kubernetes client from the stored kubeconfig.
            using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig);

            // List pods in the deployment's namespace.
            V1PodList podList = await client.CoreV1.ListNamespacedPodAsync(
                deployment.Namespace, cancellationToken: ct);

            List<PodInfo> pods = podList.Items.Select(pod => new PodInfo
            {
                Name = pod.Metadata.Name,
                Namespace = pod.Metadata.NamespaceProperty ?? deployment.Namespace,
                Status = pod.Status?.Phase ?? "Unknown",
                ReadyContainers = pod.Status?.ContainerStatuses?.Count(cs => cs.Ready) ?? 0,
                TotalContainers = pod.Spec?.Containers?.Count ?? 0,
                Restarts = pod.Status?.ContainerStatuses?.Sum(cs => cs.RestartCount) ?? 0,
                StartTime = pod.Status?.StartTime,
                Containers = (pod.Status?.ContainerStatuses ?? []).Select(cs => new ContainerInfo
                {
                    Name = cs.Name,
                    Image = cs.Image,
                    Ready = cs.Ready,
                    RestartCount = cs.RestartCount,
                    State = cs.State?.Running is not null ? "Running"
                        : cs.State?.Waiting is not null ? $"Waiting: {cs.State.Waiting.Reason}"
                        : cs.State?.Terminated is not null ? $"Terminated: {cs.State.Terminated.Reason}"
                        : "Unknown"
                }).ToList()
            }).ToList();

            return KubernetesOperationResult<List<PodInfo>>.Success(pods);
        }
        catch (Exception ex)
        {
            return KubernetesOperationResult<List<PodInfo>>.Failure($"Failed to list pods: {ex.Message}");
        }
    }

    /// <summary>
    /// Retrieves log output from a specific pod. Optionally targets a
    /// specific container in multi-container pods. Returns the last
    /// tailLines lines to keep output manageable.
    /// </summary>
    public async Task<KubernetesOperationResult<string>> GetPodLogsAsync(
        Guid deploymentId,
        string podName,
        string? containerName = null,
        int tailLines = 500,
        CancellationToken ct = default)
    {
        AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct);

        if (deployment is null)
        {
            return KubernetesOperationResult<string>.Failure("Deployment not found.");
        }

        if (string.IsNullOrEmpty(deployment.Cluster.Kubeconfig))
        {
            return KubernetesOperationResult<string>.Failure(
                "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations.");
        }

        try
        {
            using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig);

            // Fetch the last N lines of logs from the pod.
            using Stream logStream = await client.CoreV1.ReadNamespacedPodLogAsync(
                podName,
                deployment.Namespace,
                container: containerName,
                tailLines: tailLines,
                cancellationToken: ct);

            using StreamReader reader = new(logStream);
            string logs = await reader.ReadToEndAsync(ct);

            return KubernetesOperationResult<string>.Success(logs);
        }
        catch (Exception ex)
        {
            return KubernetesOperationResult<string>.Failure($"Failed to fetch logs: {ex.Message}");
        }
    }

    /// <summary>
    /// Restarts a Kubernetes Deployment by patching its pod template annotation
    /// with the current timestamp. This triggers a rolling restart — Kubernetes
    /// creates new pods and terminates old ones gradually.
    /// </summary>
    public async Task<KubernetesOperationResult> RestartDeploymentAsync(
        Guid deploymentId,
        string k8sDeploymentName,
        CancellationToken ct = default)
    {
        AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct);

        if (deployment is null)
        {
            return KubernetesOperationResult.Failure("Deployment not found.");
        }

        if (string.IsNullOrEmpty(deployment.Cluster.Kubeconfig))
        {
            return KubernetesOperationResult.Failure(
                "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations.");
        }

        try
        {
            using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig);

            // Patching the pod template annotation forces a rollout restart,
            // identical to "kubectl rollout restart deployment/<name>".
            V1Patch patch = new(
                "{\"spec\":{\"template\":{\"metadata\":{\"annotations\":" +
                $"{{\"kubectl.kubernetes.io/restartedAt\":\"{DateTime.UtcNow:O}\"}}" +
                "}}}}",
                V1Patch.PatchType.MergePatch);

            await client.AppsV1.PatchNamespacedDeploymentAsync(
                patch, k8sDeploymentName, deployment.Namespace, cancellationToken: ct);

            return KubernetesOperationResult.Success();
        }
        catch (Exception ex)
        {
            return KubernetesOperationResult.Failure($"Failed to restart deployment: {ex.Message}");
        }
    }

    /// <summary>
    /// Deletes a specific pod, which triggers Kubernetes to create a replacement
    /// (assuming the pod is managed by a Deployment/ReplicaSet). This is the
    /// equivalent of "kubectl delete pod <name>".
    /// </summary>
    public async Task<KubernetesOperationResult> DeletePodAsync(
        Guid deploymentId,
        string podName,
        CancellationToken ct = default)
    {
        AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct);

        if (deployment is null)
        {
            return KubernetesOperationResult.Failure("Deployment not found.");
        }

        if (string.IsNullOrEmpty(deployment.Cluster.Kubeconfig))
        {
            return KubernetesOperationResult.Failure(
                "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations.");
        }

        try
        {
            using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig);

            await client.CoreV1.DeleteNamespacedPodAsync(
                podName, deployment.Namespace, cancellationToken: ct);

            return KubernetesOperationResult.Success();
        }
        catch (Exception ex)
        {
            return KubernetesOperationResult.Failure($"Failed to delete pod: {ex.Message}");
        }
    }

    /// <summary>
    /// Scales a Kubernetes Deployment to the specified number of replicas.
    /// Useful for scaling up during traffic spikes or scaling down to save resources.
    /// </summary>
    public async Task<KubernetesOperationResult> ScaleDeploymentAsync(
        Guid deploymentId,
        string k8sDeploymentName,
        int replicas,
        CancellationToken ct = default)
    {
        AppDeployment? deployment = await GetDeploymentWithClusterAsync(deploymentId, ct);

        if (deployment is null)
        {
            return KubernetesOperationResult.Failure("Deployment not found.");
        }

        if (string.IsNullOrEmpty(deployment.Cluster.Kubeconfig))
        {
            return KubernetesOperationResult.Failure(
                "Cluster has no kubeconfig configured. Upload a kubeconfig to enable cluster operations.");
        }

        try
        {
            using Kubernetes client = CreateClient(deployment.Cluster.Kubeconfig);

            V1Patch patch = new(
                $"{{\"spec\":{{\"replicas\":{replicas}}}}}",
                V1Patch.PatchType.MergePatch);

            await client.AppsV1.PatchNamespacedDeploymentAsync(
                patch, k8sDeploymentName, deployment.Namespace, cancellationToken: ct);

            return KubernetesOperationResult.Success();
        }
        catch (Exception ex)
        {
            return KubernetesOperationResult.Failure($"Failed to scale deployment: {ex.Message}");
        }
    }

    // ──────── Internal ────────

    /// <summary>
    /// Creates a Kubernetes client from a raw kubeconfig YAML string.
    /// The kubeconfig is parsed in-memory — never written to disk.
    /// </summary>
    private static Kubernetes CreateClient(string kubeconfig)
    {
        using MemoryStream stream = new(System.Text.Encoding.UTF8.GetBytes(kubeconfig));
        KubernetesClientConfiguration config = KubernetesClientConfiguration.BuildConfigFromConfigFile(stream);
        return new Kubernetes(config);
    }
}
