using SysDiag = System.Diagnostics;

namespace HanaTui.Hana;

/// <summary>
/// Parameters for an Export operation.
/// </summary>
public sealed class ExportParams
{
    public string Schema { get; init; } = "";
    public string TargetPath { get; init; } = "";
    public int Threads { get; init; } = 1;
    public bool Compress { get; init; }
}

/// <summary>
/// Parameters for an Import operation.
/// </summary>
public sealed class ImportParams
{
    public string SourceSchema { get; init; } = "";
    public string? NewSchemaName { get; init; }   // non-null = rename mode
    public string SourcePath { get; init; } = "";
    public int Threads { get; init; } = 1;
    public bool Replace { get; init; }
}

/// <summary>
/// Parameters for a Copy operation.
/// </summary>
public sealed class CopyParams
{
    public string SourceSchema { get; init; } = "";
    public string TargetSchema { get; init; } = "";
    public string TempPath { get; init; } = "";
    public int Threads { get; init; } = 1;
    public bool Replace { get; init; }
}

/// <summary>
/// Parameters for Drop Schema.
/// </summary>
public sealed class DropParams
{
    public string Schema { get; init; } = "";
}

/// <summary>
/// Parameters for Rename Database (company name).
/// </summary>
public sealed class RenameDbParams
{
    public string Schema { get; init; } = "";
    public string NewCompanyName { get; init; } = "";
}

/// <summary>
/// Parameters for Backup Tenant.
/// </summary>
public sealed class BackupParams
{
    public string TargetPath { get; init; } = "";
    public int Threads { get; init; } = 1;
    public bool Compress { get; init; }
}

/// <summary>
/// High-level service that runs HANA operations using HdbCliRunner.
/// Each method accepts an operation params object and a log callback.
/// The CancellationToken is forwarded so the TaskRunner can abort.
/// </summary>
public sealed class SchemaService(string userKey)
{
    private readonly string _key = userKey;

    // -------------------------------------------------------------------------
    // Export
    // -------------------------------------------------------------------------

    public async Task<bool> ExportAsync(
        ExportParams p,
        Action<string> log,
        CancellationToken ct)
    {
        log($"[INFO] Starting export of schema '{p.Schema}'...");

        string exportDir = p.TargetPath;
        string archiveFile = "";

        if (p.Compress)
        {
            string targetDir;
            if (p.TargetPath.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
            {
                archiveFile = p.TargetPath;
                targetDir = Path.GetDirectoryName(p.TargetPath) ?? ".";
            }
            else
            {
                targetDir = p.TargetPath;
                archiveFile = Path.Combine(targetDir,
                    $"{p.Schema}_export_{DateTime.Now:yyyyMMdd_HHmmss}.tar.gz");
            }

            Directory.CreateDirectory(targetDir);
            exportDir = CreateTempDir(targetDir, $"export_{p.Schema}");
            log($"[INFO] Using temp export directory: {exportDir}");
        }
        else
        {
            Directory.CreateDirectory(exportDir);
        }

        var sql = SqlQueryBuilder.ExportSchema(p.Schema, exportDir, p.Threads);
        log($"[SQL ] {sql}");

        var exitCode = await HdbCliRunner.RunSqlStreamingAsync(_key, sql, log, ct);

        if (exitCode != 0)
        {
            log("[ERROR] Export failed.");
            if (p.Compress) SafeDelete(exportDir);
            return false;
        }

        log("[DONE] Export completed successfully.");

        if (p.Compress && !ct.IsCancellationRequested)
        {
            var ok = await CompressAsync(exportDir, archiveFile, p.Threads, log, ct);
            if (ok) SafeDelete(exportDir);
        }

        return true;
    }

    // -------------------------------------------------------------------------
    // Import
    // -------------------------------------------------------------------------

    public async Task<bool> ImportAsync(
        ImportParams p,
        Action<string> log,
        CancellationToken ct)
    {
        var mode = p.NewSchemaName is not null ? "Import & Rename" : "Import";
        log($"[INFO] Starting {mode} of schema '{p.SourceSchema}'...");

        string importDir = p.SourcePath;
        bool cleanupTemp = false;

        if (File.Exists(p.SourcePath) &&
            p.SourcePath.EndsWith(".tar.gz", StringComparison.OrdinalIgnoreCase))
        {
            importDir = CreateTempDir("/tmp", $"import_{p.SourceSchema}");
            cleanupTemp = true;
            log($"[INFO] Decompressing archive to {importDir}...");

            var ok = await DecompressAsync(p.SourcePath, importDir, p.Threads, log, ct);
            if (!ok)
            {
                SafeDelete(importDir);
                return false;
            }
        }
        else if (!Directory.Exists(importDir))
        {
            log("[ERROR] Source path is neither a directory nor a valid .tar.gz archive.");
            return false;
        }

        string sql = p.NewSchemaName is not null
            ? SqlQueryBuilder.ImportSchemaWithRename(
                p.SourceSchema, p.NewSchemaName, importDir, p.Threads, p.Replace)
            : SqlQueryBuilder.ImportSchema(p.SourceSchema, importDir, p.Threads, p.Replace);

        log($"[SQL ] {sql}");

        var exitCode = await HdbCliRunner.RunSqlStreamingAsync(_key, sql, log, ct);

        if (cleanupTemp)
        {
            log("[INFO] Cleaning up temporary files...");
            SafeDelete(importDir);
        }

        if (exitCode != 0)
        {
            log("[ERROR] Import failed.");
            return false;
        }

        log("[DONE] Import completed successfully.");
        return true;
    }

    // -------------------------------------------------------------------------
    // Copy
    // -------------------------------------------------------------------------

    public async Task<bool> CopyAsync(
        CopyParams p,
        Action<string> log,
        CancellationToken ct)
    {
        log($"[INFO] Starting copy: '{p.SourceSchema}' -> '{p.TargetSchema}'");

        // Step 1: Export
        log($"[INFO] Step 1/2: Exporting '{p.SourceSchema}'...");
        Directory.CreateDirectory(p.TempPath);
        var tempExportDir = CreateTempDir(p.TempPath, $"copy_export_{p.SourceSchema}");
        log($"[INFO] Temp export dir: {tempExportDir}");

        var exportSql = SqlQueryBuilder.ExportSchema(p.SourceSchema, tempExportDir, p.Threads);
        log($"[SQL ] {exportSql}");

        var exportCode = await HdbCliRunner.RunSqlStreamingAsync(_key, exportSql, log, ct);
        if (exportCode != 0)
        {
            log("[ERROR] Export phase failed. Aborting copy.");
            SafeDelete(tempExportDir);
            return false;
        }
        log("[DONE] Export phase completed.");

        if (ct.IsCancellationRequested) { SafeDelete(tempExportDir); return false; }

        // Step 2: Import with rename
        log($"[INFO] Step 2/2: Importing as '{p.TargetSchema}'...");
        var importSql = SqlQueryBuilder.ImportSchemaWithRename(
            p.SourceSchema, p.TargetSchema, tempExportDir, p.Threads, p.Replace);
        log($"[SQL ] {importSql}");

        var importCode = await HdbCliRunner.RunSqlStreamingAsync(_key, importSql, log, ct);

        log("[INFO] Cleaning up temporary files...");
        SafeDelete(tempExportDir);

        if (importCode != 0)
        {
            log("[ERROR] Import phase failed.");
            return false;
        }

        log($"[DONE] Successfully copied schema '{p.SourceSchema}' to '{p.TargetSchema}'.");
        return true;
    }

    // -------------------------------------------------------------------------
    // Drop
    // -------------------------------------------------------------------------

    public async Task<bool> DropAsync(
        DropParams p,
        Action<string> log,
        CancellationToken ct)
    {
        log($"[INFO] Dropping schema '{p.Schema}'...");
        var sql = SqlQueryBuilder.DropSchema(p.Schema);
        log($"[SQL ] {sql}");

        var exitCode = await HdbCliRunner.RunSqlStreamingAsync(_key, sql, log, ct);

        if (exitCode != 0)
        {
            log("[ERROR] Failed to drop schema.");
            return false;
        }

        log("[DONE] Schema successfully dropped.");
        return true;
    }

    // -------------------------------------------------------------------------
    // Rename DB (company name)
    // -------------------------------------------------------------------------

    public async Task<bool> RenameDbAsync(
        RenameDbParams p,
        Action<string> log,
        CancellationToken ct)
    {
        log($"[INFO] Renaming company in schema '{p.Schema}' to '{p.NewCompanyName}'...");

        var q1 = SqlQueryBuilder.RenameCompanyInCinf(p.Schema, p.NewCompanyName);
        log($"[SQL ] {q1}");
        var r1 = await HdbCliRunner.RunSqlStreamingAsync(_key, q1, log, ct);
        if (r1 != 0) { log("[WARN] CINF update may have failed (table may not exist)."); }

        if (ct.IsCancellationRequested) return false;

        var q2 = SqlQueryBuilder.RenameCompanyInOadm(p.Schema, p.NewCompanyName);
        log($"[SQL ] {q2}");
        var r2 = await HdbCliRunner.RunSqlStreamingAsync(_key, q2, log, ct);
        if (r2 != 0) { log("[WARN] OADM update may have failed (table may not exist)."); }

        log("[DONE] Database rename completed.");
        return true;
    }

    // -------------------------------------------------------------------------
    // Backup Tenant
    // -------------------------------------------------------------------------

    public async Task<bool> BackupAsync(
        BackupParams p,
        Action<string> log,
        CancellationToken ct)
    {
        log("[INFO] Fetching tenant name...");

        var tenantResult = await GetTenantNameAsync();
        if (string.IsNullOrEmpty(tenantResult))
        {
            log("[ERROR] Could not retrieve HANA tenant name.");
            return false;
        }

        var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
        string backupDir = p.TargetPath;
        string archiveFile = "";

        if (p.Compress)
        {
            backupDir = CreateTempDir(p.TargetPath, $"{tenantResult}_backup_{timestamp}");
            archiveFile = Path.Combine(p.TargetPath, $"{tenantResult}_backup_{timestamp}.tar.gz");
            log($"[INFO] Using temp backup directory: {backupDir}");
        }

        Directory.CreateDirectory(backupDir);
        var backupPrefix = Path.Combine(backupDir, $"backup_{tenantResult}_{timestamp}");

        log($"[INFO] Starting backup of tenant '{tenantResult}'...");
        var sql = SqlQueryBuilder.BackupTenant(backupPrefix);
        log($"[SQL ] {sql}");

        var exitCode = await HdbCliRunner.RunSqlStreamingAsync(_key, sql, log, ct);

        if (exitCode != 0)
        {
            log("[ERROR] Backup failed.");
            if (p.Compress) SafeDelete(backupDir);
            return false;
        }

        log("[DONE] Backup completed successfully.");

        if (p.Compress && !ct.IsCancellationRequested)
        {
            var ok = await CompressDir(backupDir, archiveFile, p.Threads, log, ct);
            if (ok) SafeDelete(backupDir);
        }

        return true;
    }

    private async Task<string> GetTenantNameAsync()
    {
        var sql = SqlQueryBuilder.GetTenantName();
        var (success, output) = RunCapture(HdbClientLocator.HdbSql, $"-U \"{_key}\" \"{sql}\"");
        if (!success) return "";

        foreach (var line in output.Split('\n'))
        {
            var clean = line.Trim().Trim('"').Trim();
            if (string.IsNullOrWhiteSpace(clean)) continue;
            if (clean.StartsWith("DATABASE_NAME", StringComparison.OrdinalIgnoreCase)) continue;
            if (clean.StartsWith('-')) continue;
            if (clean.Contains("row", StringComparison.OrdinalIgnoreCase)) continue;
            return clean;
        }

        return "";
    }

    private static (bool, string) RunCapture(string exe, string args)
    {
        try
        {
        var psi = new SysDiag.ProcessStartInfo(exe)
        {
            Arguments = args,
            RedirectStandardOutput = true,
            RedirectStandardError = true,
            UseShellExecute = false,
            CreateNoWindow = true,
        };
        using var proc = SysDiag.Process.Start(psi);
            if (proc is null) return (false, "");
            var stdout = proc.StandardOutput.ReadToEnd();
            var stderr = proc.StandardError.ReadToEnd();
            proc.WaitForExit();
            return (proc.ExitCode == 0, proc.ExitCode == 0 ? stdout : stderr);
        }
        catch (Exception ex) { return (false, ex.Message); }
    }

    private static async Task<bool> CompressAsync(
        string sourceDir, string archiveFile, int threads, Action<string> log, CancellationToken ct)
    {
        log($"[INFO] Compressing to {archiveFile}...");
        return await CompressDir(sourceDir, archiveFile, threads, log, ct);
    }

    private static async Task<bool> CompressDir(
        string sourceDir, string archiveFile, int threads, Action<string> log, CancellationToken ct)
    {
        Directory.CreateDirectory(Path.GetDirectoryName(archiveFile) ?? ".");

        bool hasPigz = IsOnPath("pigz");
        string exe, args;

        if (hasPigz)
        {
            exe = "tar";
            args = $"-I \"pigz -p {threads}\" -cf \"{archiveFile}\" -C \"{Path.GetDirectoryName(sourceDir)}\" \"{Path.GetFileName(sourceDir)}\"";
        }
        else
        {
            exe = "tar";
            args = $"-czf \"{archiveFile}\" -C \"{Path.GetDirectoryName(sourceDir)}\" \"{Path.GetFileName(sourceDir)}\"";
        }

        log($"[INFO] Using: {exe} {args}");
        var code = await HdbCliRunner.RunCommandStreamingAsync(exe, args, log, ct);

        if (code == 0)
        {
            log("[DONE] Compression successful.");
            return true;
        }

        log("[ERROR] Compression failed.");
        return false;
    }

    private static async Task<bool> DecompressAsync(
        string archiveFile, string targetDir, int threads, Action<string> log, CancellationToken ct)
    {
        bool hasPigz = IsOnPath("pigz");
        string exe = "tar";
        string args;

        if (hasPigz)
            args = $"-I \"pigz -p {threads}\" -xf \"{archiveFile}\" -C \"{targetDir}\" --strip-components=1";
        else
            args = $"-xzf \"{archiveFile}\" -C \"{targetDir}\" --strip-components=1";

        var code = await HdbCliRunner.RunCommandStreamingAsync(exe, args, log, ct);

        if (code == 0) { log("[DONE] Decompression successful."); return true; }
        log("[ERROR] Decompression failed.");
        return false;
    }

    private static string CreateTempDir(string parent, string prefix)
    {
        // Generate a unique temp dir similar to mktemp -d
        string path;
        do
        {
            path = Path.Combine(parent, $"{prefix}_{Path.GetRandomFileName()[..6]}");
        } while (Directory.Exists(path));

        Directory.CreateDirectory(path);
        return path;
    }

    private static void SafeDelete(string path)
    {
        try { if (Directory.Exists(path)) Directory.Delete(path, true); }
        catch { /* best effort */ }
    }

    private static bool IsOnPath(string binary)
    {
        var pathEnv = Environment.GetEnvironmentVariable("PATH") ?? "";
        foreach (var dir in pathEnv.Split(':'))
        {
            if (File.Exists(Path.Combine(dir, binary)))
                return true;
        }
        return false;
    }
}
