using System.Text;
using EntKube.Web.Data;
using Microsoft.EntityFrameworkCore;

namespace EntKube.Web.Services;

/// <summary>
/// Manages app-level governance: namespace assignment, ResourceQuota,
/// NetworkPolicy, and RBAC (ServiceAccount/Role/RoleBinding).
///
/// All governance data is stored in the database and can be applied to any
/// cluster where the app has deployments via ApplyToClusterAsync.
/// </summary>
public class AppGovernanceService(
    IDbContextFactory<ApplicationDbContext> dbFactory,
    ILogger<AppGovernanceService> logger)
{
    // ── Namespace ────────────────────────────────────────────────────────────

    public async Task<bool> SetNamespaceAsync(
        Guid appId, string? ns, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        App? app = await db.Apps.FindAsync([appId], ct);
        if (app is null) return false;
        app.Namespace = string.IsNullOrWhiteSpace(ns) ? null : ns.Trim().ToLowerInvariant();
        await db.SaveChangesAsync(ct);
        return true;
    }

    // ── Quota ─────────────────────────────────────────────────────────────────

    public async Task<AppQuota?> GetQuotaAsync(Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.AppQuotas.FirstOrDefaultAsync(q => q.AppId == appId, ct);
    }

    public async Task<AppQuota> SaveQuotaAsync(
        Guid appId,
        string? cpuRequest, string? cpuLimit,
        string? memRequest, string? memLimit,
        int? maxPods, int? maxPvcs,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppQuota? existing = await db.AppQuotas.FirstOrDefaultAsync(q => q.AppId == appId, ct);

        if (existing is null)
        {
            existing = new AppQuota { Id = Guid.NewGuid(), AppId = appId };
            db.AppQuotas.Add(existing);
        }

        existing.CpuRequest    = Blank(cpuRequest);
        existing.CpuLimit      = Blank(cpuLimit);
        existing.MemoryRequest = Blank(memRequest);
        existing.MemoryLimit   = Blank(memLimit);
        existing.MaxPods       = maxPods;
        existing.MaxPvcs       = maxPvcs;
        existing.UpdatedAt     = DateTime.UtcNow;

        await db.SaveChangesAsync(ct);
        return existing;
    }

    public async Task<bool> DeleteQuotaAsync(Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppQuota? q = await db.AppQuotas.FirstOrDefaultAsync(x => x.AppId == appId, ct);
        if (q is null) return false;
        db.AppQuotas.Remove(q);
        await db.SaveChangesAsync(ct);
        return true;
    }

    // ── Network policies ──────────────────────────────────────────────────────

    public async Task<List<AppNetworkPolicy>> GetNetworkPoliciesAsync(
        Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.AppNetworkPolicies
            .Where(p => p.AppId == appId)
            .OrderBy(p => p.Name)
            .ToListAsync(ct);
    }

    public async Task<AppNetworkPolicy> AddNetworkPolicyAsync(
        Guid appId, string name, AppNetworkPolicyType type,
        string? allowFromNs = null, string? customYaml = null,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppNetworkPolicy policy = new()
        {
            Id = Guid.NewGuid(),
            AppId = appId,
            Name = name.Trim().ToLowerInvariant(),
            PolicyType = type,
            AllowFromNamespace = Blank(allowFromNs),
            CustomYaml = customYaml
        };
        db.AppNetworkPolicies.Add(policy);
        await db.SaveChangesAsync(ct);
        return policy;
    }

    public async Task<bool> DeleteNetworkPolicyAsync(Guid policyId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppNetworkPolicy? p = await db.AppNetworkPolicies.FindAsync([policyId], ct);
        if (p is null) return false;
        db.AppNetworkPolicies.Remove(p);
        await db.SaveChangesAsync(ct);
        return true;
    }

    // ── RBAC ──────────────────────────────────────────────────────────────────

    public async Task<AppRbacPolicy?> GetRbacPolicyAsync(Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        return await db.AppRbacPolicies
            .Include(p => p.Rules)
            .FirstOrDefaultAsync(p => p.AppId == appId, ct);
    }

    public async Task<AppRbacPolicy> SaveRbacPolicyAsync(
        Guid appId, string serviceAccountName, bool autoMount,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppRbacPolicy? existing = await db.AppRbacPolicies
            .FirstOrDefaultAsync(p => p.AppId == appId, ct);

        if (existing is null)
        {
            existing = new AppRbacPolicy
            {
                Id = Guid.NewGuid(),
                AppId = appId,
                ServiceAccountName = serviceAccountName.Trim().ToLowerInvariant()
            };
            db.AppRbacPolicies.Add(existing);
        }
        else
        {
            existing.ServiceAccountName = serviceAccountName.Trim().ToLowerInvariant();
        }

        existing.AutoMountToken = autoMount;
        await db.SaveChangesAsync(ct);
        return existing;
    }

    public async Task<AppRbacRule> AddRbacRuleAsync(
        Guid rbacPolicyId, string apiGroups, string resources, string verbs,
        CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppRbacRule rule = new()
        {
            Id = Guid.NewGuid(),
            AppRbacPolicyId = rbacPolicyId,
            ApiGroups = apiGroups.Trim(),
            Resources = resources.Trim(),
            Verbs = verbs.Trim()
        };
        db.AppRbacRules.Add(rule);
        await db.SaveChangesAsync(ct);
        return rule;
    }

    public async Task<bool> DeleteRbacRuleAsync(Guid ruleId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppRbacRule? r = await db.AppRbacRules.FindAsync([ruleId], ct);
        if (r is null) return false;
        db.AppRbacRules.Remove(r);
        await db.SaveChangesAsync(ct);
        return true;
    }

    public async Task<bool> DeleteRbacPolicyAsync(Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();
        AppRbacPolicy? p = await db.AppRbacPolicies.FirstOrDefaultAsync(x => x.AppId == appId, ct);
        if (p is null) return false;
        db.AppRbacPolicies.Remove(p);
        await db.SaveChangesAsync(ct);
        return true;
    }

    // ── Load all governance for an app ────────────────────────────────────────

    public async Task<AppGovernanceData> LoadAsync(Guid appId, CancellationToken ct = default)
    {
        using ApplicationDbContext db = dbFactory.CreateDbContext();

        App? app = await db.Apps.FindAsync([appId], ct);

        return new AppGovernanceData
        {
            Namespace      = app?.Namespace,
            Quota          = await GetQuotaAsync(appId, ct),
            NetworkPolicies = await GetNetworkPoliciesAsync(appId, ct),
            RbacPolicy     = await GetRbacPolicyAsync(appId, ct)
        };
    }

    // ── K8s apply ─────────────────────────────────────────────────────────────

    /// <summary>
    /// Applies all governance resources (Namespace, ResourceQuota, NetworkPolicies,
    /// ServiceAccount/Role/RoleBinding) to the given cluster using kubectl apply.
    /// Idempotent — safe to run multiple times.
    /// </summary>
    public async Task<(bool Success, string Output)> ApplyToClusterAsync(
        Guid appId, KubernetesCluster cluster, CancellationToken ct = default)
    {
        AppGovernanceData data = await LoadAsync(appId, ct);

        if (string.IsNullOrWhiteSpace(data.Namespace))
            return (false, "App has no namespace configured. Set a namespace before applying.");

        if (string.IsNullOrWhiteSpace(cluster.Kubeconfig))
            return (false, "Cluster has no kubeconfig configured.");

        string yaml = BuildManifest(data);
        string kubeconfigPath = Path.Combine(Path.GetTempPath(), $"entkube-gov-{Guid.NewGuid():N}.kubeconfig");
        string manifestPath   = Path.Combine(Path.GetTempPath(), $"entkube-gov-{Guid.NewGuid():N}.yaml");

        try
        {
            await File.WriteAllTextAsync(kubeconfigPath, cluster.Kubeconfig, ct);
            await File.WriteAllTextAsync(manifestPath, yaml, ct);

            System.Diagnostics.ProcessStartInfo psi = new("kubectl",
                $"apply -f {manifestPath} --kubeconfig {kubeconfigPath}")
            {
                RedirectStandardOutput = true,
                RedirectStandardError  = true,
                UseShellExecute        = false,
                CreateNoWindow         = true
            };

            using System.Diagnostics.Process proc = new() { StartInfo = psi };
            StringBuilder output = new();
            proc.OutputDataReceived += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); };
            proc.ErrorDataReceived  += (_, e) => { if (e.Data is not null) output.AppendLine(e.Data); };
            proc.Start();
            proc.BeginOutputReadLine();
            proc.BeginErrorReadLine();
            await proc.WaitForExitAsync(ct);

            bool ok = proc.ExitCode == 0;
            string result = output.ToString().TrimEnd();

            if (ok)
                logger.LogInformation("Governance applied to {Cluster}/{Namespace}", cluster.Name, data.Namespace);
            else
                logger.LogWarning("Governance apply failed for {Cluster}: {Output}", cluster.Name, result);

            return (ok, result);
        }
        finally
        {
            if (File.Exists(kubeconfigPath)) File.Delete(kubeconfigPath);
            if (File.Exists(manifestPath))   File.Delete(manifestPath);
        }
    }

    // ── Manifest builder ──────────────────────────────────────────────────────

    public static string BuildManifest(AppGovernanceData data)
    {
        var docs = new List<string>();
        string ns = data.Namespace!;

        // 1. Namespace
        docs.Add(Y(
            "apiVersion: v1",
            "kind: Namespace",
            "metadata:",
            $"  name: {ns}"));

        // 2. ResourceQuota
        if (data.Quota is { } q)
        {
            var hard = new StringBuilder();
            if (!string.IsNullOrWhiteSpace(q.CpuRequest))    hard.AppendLine($"    requests.cpu: \"{q.CpuRequest}\"");
            if (!string.IsNullOrWhiteSpace(q.CpuLimit))      hard.AppendLine($"    limits.cpu: \"{q.CpuLimit}\"");
            if (!string.IsNullOrWhiteSpace(q.MemoryRequest)) hard.AppendLine($"    requests.memory: \"{q.MemoryRequest}\"");
            if (!string.IsNullOrWhiteSpace(q.MemoryLimit))   hard.AppendLine($"    limits.memory: \"{q.MemoryLimit}\"");
            if (q.MaxPods.HasValue)  hard.AppendLine($"    pods: \"{q.MaxPods}\"");
            if (q.MaxPvcs.HasValue)  hard.AppendLine($"    persistentvolumeclaims: \"{q.MaxPvcs}\"");

            if (hard.Length > 0)
            {
                docs.Add(Y(
                    "apiVersion: v1",
                    "kind: ResourceQuota",
                    "metadata:",
                    $"  name: app-quota",
                    $"  namespace: {ns}",
                    "spec:",
                    "  hard:",
                    hard.ToString().TrimEnd()));
            }
        }

        // 3. NetworkPolicies
        foreach (AppNetworkPolicy np in data.NetworkPolicies)
        {
            string header = Y(
                "apiVersion: networking.k8s.io/v1",
                "kind: NetworkPolicy",
                "metadata:",
                $"  name: {np.Name}",
                $"  namespace: {ns}",
                "spec:",
                "  podSelector: {}");

            string policyYaml = np.PolicyType switch
            {
                AppNetworkPolicyType.DenyAll =>
                    header + "\n  policyTypes:\n    - Ingress\n    - Egress",

                AppNetworkPolicyType.AllowFromIngress =>
                    header + Y(
                        "  ingress:",
                        "    - from:",
                        "        - namespaceSelector:",
                        "            matchExpressions:",
                        "              - key: kubernetes.io/metadata.name",
                        "                operator: In",
                        "                values:",
                        "                  - ingress-nginx",
                        "                  - traefik"),

                AppNetworkPolicyType.AllowFromSameNamespace =>
                    header + Y(
                        "  ingress:",
                        "    - from:",
                        "        - podSelector: {}"),

                AppNetworkPolicyType.AllowFromNamespace
                    when !string.IsNullOrWhiteSpace(np.AllowFromNamespace) =>
                    header + Y(
                        "  ingress:",
                        "    - from:",
                        "        - namespaceSelector:",
                        "            matchLabels:",
                        $"              kubernetes.io/metadata.name: {np.AllowFromNamespace}"),

                AppNetworkPolicyType.Custom when !string.IsNullOrWhiteSpace(np.CustomYaml) =>
                    np.CustomYaml,

                _ => string.Empty
            };

            if (!string.IsNullOrWhiteSpace(policyYaml))
                docs.Add(policyYaml.Trim());
        }

        // 4. ServiceAccount + Role + RoleBinding
        if (data.RbacPolicy is { } rbac && rbac.Rules.Count > 0)
        {
            string sa = rbac.ServiceAccountName;

            docs.Add(Y(
                "apiVersion: v1",
                "kind: ServiceAccount",
                "metadata:",
                $"  name: {sa}",
                $"  namespace: {ns}",
                $"automountServiceAccountToken: {rbac.AutoMountToken.ToString().ToLowerInvariant()}"));

            var rulesYaml = new StringBuilder();
            foreach (AppRbacRule rule in rbac.Rules)
            {
                string apiGroupsYaml = string.Join(", ",
                    (string.IsNullOrWhiteSpace(rule.ApiGroups)
                        ? [""]
                        : rule.ApiGroups.Split(',', StringSplitOptions.RemoveEmptyEntries))
                    .Select(g => $"\"{g.Trim()}\""));

                string resourcesYaml = string.Join(", ",
                    rule.Resources.Split(',', StringSplitOptions.RemoveEmptyEntries)
                        .Select(r => $"\"{r.Trim()}\""));

                string verbsYaml = string.Join(", ",
                    rule.Verbs.Split(',', StringSplitOptions.RemoveEmptyEntries)
                        .Select(v => $"\"{v.Trim()}\""));

                rulesYaml.AppendLine($"  - apiGroups: [{apiGroupsYaml}]");
                rulesYaml.AppendLine($"    resources: [{resourcesYaml}]");
                rulesYaml.AppendLine($"    verbs: [{verbsYaml}]");
            }

            docs.Add(Y(
                "apiVersion: rbac.authorization.k8s.io/v1",
                "kind: Role",
                "metadata:",
                $"  name: {sa}-role",
                $"  namespace: {ns}",
                "rules:",
                rulesYaml.ToString().TrimEnd()));

            docs.Add(Y(
                "apiVersion: rbac.authorization.k8s.io/v1",
                "kind: RoleBinding",
                "metadata:",
                $"  name: {sa}-binding",
                $"  namespace: {ns}",
                "subjects:",
                "  - kind: ServiceAccount",
                $"    name: {sa}",
                $"    namespace: {ns}",
                "roleRef:",
                "  kind: Role",
                $"  name: {sa}-role",
                "  apiGroup: rbac.authorization.k8s.io"));
        }

        return string.Join("\n---\n", docs);
    }

    // Join lines into a YAML document string.
    private static string Y(params string[] lines) => string.Join("\n", lines);

    private static string? Blank(string? s) =>
        string.IsNullOrWhiteSpace(s) ? null : s.Trim();
}

/// <summary>Snapshot of all governance data for an app.</summary>
public class AppGovernanceData
{
    public string? Namespace { get; init; }
    public AppQuota? Quota { get; init; }
    public List<AppNetworkPolicy> NetworkPolicies { get; init; } = [];
    public AppRbacPolicy? RbacPolicy { get; init; }
}
