using System.Text.RegularExpressions;
using Microsoft.Extensions.Logging;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Renders a Docker Compose file by loading a template from the git repo and substituting
/// all {{PLACEHOLDER}} tokens with values from RenderContext.
/// The template file expected in the repo is <c>template.yml</c>.
/// Call <see cref="GetTemplateYaml"/> to obtain the canonical template to commit to your repo.
/// </summary>
public class ComposeRenderService
{
    private readonly ILogger<ComposeRenderService> _logger;

    public ComposeRenderService(ILogger<ComposeRenderService> logger)
    {
        _logger = logger;
    }

    /// <summary>
    /// Substitutes all {{PLACEHOLDER}} tokens in <paramref name="templateYaml"/> and returns
    /// the final compose YAML ready for deployment.
    /// </summary>
    public string Render(string templateYaml, RenderContext ctx)
    {
        _logger.LogInformation("Rendering Compose for stack {StackName} from template", ctx.StackName);

        if (string.IsNullOrWhiteSpace(templateYaml))
            throw new ArgumentException("Template YAML is empty. Ensure template.yml exists in the configured git repository.");

        var nfsOpts = BuildNfsOpts(ctx);
        var nfsDevicePrefix = BuildNfsDevicePrefix(ctx);

        return templateYaml
            .Replace("{{ABBREV}}",                    ctx.CustomerAbbrev)
            .Replace("{{CUSTOMER_NAME}}",             ctx.CustomerName)
            .Replace("{{STACK_NAME}}",                ctx.StackName)
            .Replace("{{CMS_SERVER_NAME}}",           ctx.CmsServerName)
            .Replace("{{HOST_HTTP_PORT}}",            ctx.HostHttpPort.ToString())
            .Replace("{{CMS_IMAGE}}",                 ctx.CmsImage)
            .Replace("{{MEMCACHED_IMAGE}}",           ctx.MemcachedImage)
            .Replace("{{QUICKCHART_IMAGE}}",          ctx.QuickChartImage)
            .Replace("{{NEWT_IMAGE}}",                ctx.NewtImage)
            .Replace("{{THEME_HOST_PATH}}",           ctx.ThemeHostPath)
            .Replace("{{MYSQL_HOST}}",                ctx.MySqlHost)
            .Replace("{{MYSQL_PORT}}",                ctx.MySqlPort)
            .Replace("{{MYSQL_DATABASE}}",            ctx.MySqlDatabase)
            .Replace("{{MYSQL_USER}}",                ctx.MySqlUser)
            .Replace("{{MYSQL_PASSWORD}}",            ctx.MySqlPassword)
            .Replace("{{SMTP_SERVER}}",               ctx.SmtpServer)
            .Replace("{{SMTP_USERNAME}}",             ctx.SmtpUsername)
            .Replace("{{SMTP_PASSWORD}}",             ctx.SmtpPassword)
            .Replace("{{SMTP_USE_TLS}}",              ctx.SmtpUseTls)
            .Replace("{{SMTP_USE_STARTTLS}}",         ctx.SmtpUseStartTls)
            .Replace("{{SMTP_REWRITE_DOMAIN}}",       ctx.SmtpRewriteDomain)
            .Replace("{{SMTP_HOSTNAME}}",             ctx.SmtpHostname)
            .Replace("{{SMTP_FROM_LINE_OVERRIDE}}",   ctx.SmtpFromLineOverride)
            .Replace("{{PHP_POST_MAX_SIZE}}",         ctx.PhpPostMaxSize)
            .Replace("{{PHP_UPLOAD_MAX_FILESIZE}}",   ctx.PhpUploadMaxFilesize)
            .Replace("{{PHP_MAX_EXECUTION_TIME}}",    ctx.PhpMaxExecutionTime)
            .Replace("{{PANGOLIN_ENDPOINT}}",         ctx.PangolinEndpoint)
            .Replace("{{NEWT_ID}}",                   ctx.NewtId ?? "CONFIGURE_ME")
            .Replace("{{NEWT_SECRET}}",               ctx.NewtSecret ?? "CONFIGURE_ME")
            .Replace("{{NFS_DEVICE_PREFIX}}",         nfsDevicePrefix)
            .Replace("{{NFS_OPTS}}",                  nfsOpts)
            // ── Legacy CIFS token compatibility ─────────────────────────────
            // External git template repos may still contain old CIFS tokens.
            // Map them to NFS equivalents so those templates render correctly.
            .Replace("{{CIFS_SERVER}}",              ctx.NfsServer ?? string.Empty)
            .Replace("{{CIFS_SHARE_NAME}}",          BuildLegacySharePath(ctx))
            .Replace("{{CIFS_SHARE_BASE_PATH}}",     "/" + BuildLegacySharePath(ctx))
            .Replace("{{CIFS_USERNAME}}",            string.Empty)
            .Replace("{{CIFS_PASSWORD}}",            string.Empty)
            .Replace("{{CIFS_OPTS}}",                nfsOpts);
    }

    /// <summary>
    /// Builds a legacy-compatible share path from NFS export + folder for old CIFS templates.
    /// Maps NFS export/folder to the path that was previously the CIFS share name/folder.
    /// </summary>
    private static string BuildLegacySharePath(RenderContext ctx)
    {
        var export = (ctx.NfsExport ?? string.Empty).Trim('/');
        var folder = (ctx.NfsExportFolder ?? string.Empty).Trim('/');
        return string.IsNullOrEmpty(folder) ? export : $"{export}/{folder}";
    }

    /// <summary>
    /// Builds the NFS mount options string for Docker volume driver_opts.
    /// Format: "addr=&lt;server&gt;,nfsvers=4,proto=tcp[,extraOptions]".
    /// </summary>
    private static string BuildNfsOpts(RenderContext ctx)
    {
        if (string.IsNullOrWhiteSpace(ctx.NfsServer))
            return string.Empty;

        var opts = $"addr={ctx.NfsServer},nfsvers=4,proto=tcp";
        if (!string.IsNullOrWhiteSpace(ctx.NfsExtraOptions))
            opts += $",{ctx.NfsExtraOptions}";
        return opts;
    }

    /// <summary>
    /// Builds the NFS device prefix used in volume definitions.
    /// Format: ":/export[/subfolder]"  (the colon is part of the device path for NFS).
    /// e.g. ":/srv/nfs/ots_cms" or ":/srv/nfs".
    /// </summary>
    private static string BuildNfsDevicePrefix(RenderContext ctx)
    {
        var export = (ctx.NfsExport ?? string.Empty).Trim('/');
        var folder = (ctx.NfsExportFolder ?? string.Empty).Trim('/');
        var path = string.IsNullOrEmpty(folder) ? export : $"{export}/{folder}";
        // When path is empty the prefix must be ":" with no trailing slash — the template
        // already supplies the leading "/" before {{ABBREV}}, so ":" + "/ots/..." = ":/ots/..."
        // (correct). Returning ":/" would produce "://ots/..." which Docker rejects.
        return string.IsNullOrEmpty(path) ? ":" : $":/{path}";
    }

    /// <summary>
    /// Extracts NFS volume device paths from rendered compose YAML, then strips the
    /// NFS export prefix to return just the relative folder paths that need to exist
    /// on the NFS server.  Works regardless of the template's naming convention
    /// (hierarchical <c>ots/cms-custom</c> vs flat <c>ots-cms-custom</c>).
    /// </summary>
    public static List<string> ExtractNfsDeviceFolders(string renderedYaml, string nfsExport, string? nfsExportFolder = null)
    {
        // NFS device lines look like:  device: ":/mnt/Export/folder/ots-cms-custom"
        // The colon prefix is the NFS device convention.
        var matches = Regex.Matches(renderedYaml, @"device:\s*""?:(/[^""]+)""?", RegexOptions.IgnoreCase);
        var export = (nfsExport ?? string.Empty).Trim('/');
        var subFolder = (nfsExportFolder ?? string.Empty).Trim('/');
        var prefix = string.IsNullOrEmpty(subFolder) ? $"/{export}" : $"/{export}/{subFolder}";

        var folders = new List<string>();
        foreach (Match match in matches)
        {
            var devicePath = match.Groups[1].Value;  // e.g. /mnt/DS-SwarmVolumes/Volumes/ots-cms-custom
            if (devicePath.StartsWith(prefix + "/"))
            {
                // Strip the export prefix to get the relative folder path
                var relative = devicePath[(prefix.Length + 1)..]; // e.g. ots-cms-custom or ots/cms-custom
                if (!string.IsNullOrEmpty(relative))
                    folders.Add(relative);
            }
        }

        return folders;
    }

    /// <summary>
    /// Returns the canonical <c>template.yml</c> content with all placeholders.
    /// Commit this file to the root of your template git repository.
    /// </summary>
    public static string GetTemplateYaml() => TemplateYaml;

    // ── Canonical template ──────────────────────────────────────────────────

    public const string TemplateYaml =
        """
        # Customer: {{CUSTOMER_NAME}}
        version: "3.9"

        services:

          {{ABBREV}}-web:
            image: {{CMS_IMAGE}}
            environment:
              CMS_USE_MEMCACHED: "true"
              MEMCACHED_HOST: memcached
              MYSQL_HOST: {{MYSQL_HOST}}
              MYSQL_PORT: "{{MYSQL_PORT}}"
              MYSQL_DATABASE: {{MYSQL_DATABASE}}
              MYSQL_USER: {{MYSQL_USER}}
              MYSQL_PASSWORD: {{MYSQL_PASSWORD}}
              CMS_SERVER_NAME: {{CMS_SERVER_NAME}}
              CMS_SMTP_SERVER: {{SMTP_SERVER}}
              CMS_SMTP_USERNAME: {{SMTP_USERNAME}}
              CMS_SMTP_PASSWORD: {{SMTP_PASSWORD}}
              CMS_SMTP_USE_TLS: {{SMTP_USE_TLS}}
              CMS_SMTP_USE_STARTTLS: {{SMTP_USE_STARTTLS}}
              CMS_SMTP_REWRITE_DOMAIN: {{SMTP_REWRITE_DOMAIN}}
              CMS_SMTP_HOSTNAME: {{SMTP_HOSTNAME}}
              CMS_SMTP_FROM_LINE_OVERRIDE: {{SMTP_FROM_LINE_OVERRIDE}}
              CMS_PHP_POST_MAX_SIZE: {{PHP_POST_MAX_SIZE}}
              CMS_PHP_UPLOAD_MAX_FILESIZE: {{PHP_UPLOAD_MAX_FILESIZE}}
              CMS_PHP_MAX_EXECUTION_TIME: "{{PHP_MAX_EXECUTION_TIME}}"
            secrets:
              - {{ABBREV}}-cms-db-user
              - global_mysql_host
              - global_mysql_port
            volumes:
              - {{ABBREV}}-cms-custom:/var/www/cms/custom
              - {{ABBREV}}-cms-backup:/var/www/backup
              - {{THEME_HOST_PATH}}:/var/www/cms/web/theme/custom
              - {{ABBREV}}-cms-library:/var/www/cms/library
              - {{ABBREV}}-cms-userscripts:/var/www/cms/web/userscripts
              - {{ABBREV}}-cms-ca-certs:/var/www/cms/ca-certs
            ports:
              - "{{HOST_HTTP_PORT}}:80"
            healthcheck:
              test: ["CMD-SHELL", "curl -fsS --max-time 5 http://web:80/about | grep -Eo 'v?[0-9]+(\\.[0-9]+)+' >/dev/null || exit 1"]
              interval: 10s
              timeout: 5s
              retries: 5
              start_period: 30s
            networks:
              {{ABBREV}}-net:
                aliases:
                  - web
            deploy:
              restart_policy:
                condition: any
              resources:
                limits:
                  memory: 1G

          {{ABBREV}}-memcached:
            image: {{MEMCACHED_IMAGE}}
            command: [memcached, -m, "15"]
            networks:
              {{ABBREV}}-net:
                aliases:
                  - memcached
            deploy:
              restart_policy:
                condition: any
              resources:
                limits:
                  memory: 100M

          {{ABBREV}}-quickchart:
            image: {{QUICKCHART_IMAGE}}
            networks:
              {{ABBREV}}-net:
                aliases:
                  - quickchart
            deploy:
              restart_policy:
                condition: any

          {{ABBREV}}-newt:
            image: {{NEWT_IMAGE}}
            environment:
              PANGOLIN_ENDPOINT: {{PANGOLIN_ENDPOINT}}
              NEWT_ID: {{NEWT_ID}}
              NEWT_SECRET: {{NEWT_SECRET}}
            depends_on:
              - {{ABBREV}}-web
            networks:
              {{ABBREV}}-net: {}
            deploy:
              restart_policy:
                condition: any

        networks:
          {{ABBREV}}-net:
            driver: overlay
            attachable: "false"

        volumes:
          {{ABBREV}}-cms-custom:
            driver: local
            driver_opts:
              type: nfs
              device: "{{NFS_DEVICE_PREFIX}}/{{ABBREV}}/cms-custom"
              o: "{{NFS_OPTS}}"
          {{ABBREV}}-cms-backup:
            driver: local
            driver_opts:
              type: nfs
              device: "{{NFS_DEVICE_PREFIX}}/{{ABBREV}}/cms-backup"
              o: "{{NFS_OPTS}}"
          {{ABBREV}}-cms-library:
            driver: local
            driver_opts:
              type: nfs
              device: "{{NFS_DEVICE_PREFIX}}/{{ABBREV}}/cms-library"
              o: "{{NFS_OPTS}}"
          {{ABBREV}}-cms-userscripts:
            driver: local
            driver_opts:
              type: nfs
              device: "{{NFS_DEVICE_PREFIX}}/{{ABBREV}}/cms-userscripts"
              o: "{{NFS_OPTS}}"
          {{ABBREV}}-cms-ca-certs:
            driver: local
            driver_opts:
              type: nfs
              device: "{{NFS_DEVICE_PREFIX}}/{{ABBREV}}/cms-ca-certs"
              o: "{{NFS_OPTS}}"

        secrets:
          {{ABBREV}}-cms-db-user:
            external: true
          global_mysql_host:
            external: true
          global_mysql_port:
            external: true
        """;
}

/// <summary>Context object with all inputs needed to render a Compose file.</summary>
public class RenderContext
{
    public string CustomerName { get; set; } = string.Empty;
    public string CustomerAbbrev { get; set; } = string.Empty;
    public string StackName { get; set; } = string.Empty;
    public string CmsServerName { get; set; } = string.Empty;
    public int HostHttpPort { get; set; } = 80;

    // Docker images
    public string CmsImage { get; set; } = "ghcr.io/xibosignage/xibo-cms:release-4.2.3";
    public string MemcachedImage { get; set; } = "memcached:alpine";
    public string QuickChartImage { get; set; } = "ianw/quickchart";
    public string NewtImage { get; set; } = "fosrl/newt";

    // Theme bind mount path on host
    public string ThemeHostPath { get; set; } = string.Empty;

    // MySQL (external server)
    public string MySqlHost { get; set; } = string.Empty;
    public string MySqlPort { get; set; } = "3306";
    public string MySqlDatabase { get; set; } = "cms";
    public string MySqlUser { get; set; } = "cms";
    public string MySqlPassword { get; set; } = string.Empty;

    // SMTP
    public string SmtpServer { get; set; } = string.Empty;
    public string SmtpUsername { get; set; } = string.Empty;
    public string SmtpPassword { get; set; } = string.Empty;
    public string SmtpUseTls { get; set; } = "YES";
    public string SmtpUseStartTls { get; set; } = "YES";
    public string SmtpRewriteDomain { get; set; } = string.Empty;
    public string SmtpHostname { get; set; } = string.Empty;
    public string SmtpFromLineOverride { get; set; } = "NO";

    // PHP settings
    public string PhpPostMaxSize { get; set; } = "10G";
    public string PhpUploadMaxFilesize { get; set; } = "10G";
    public string PhpMaxExecutionTime { get; set; } = "600";

    // Pangolin / Newt
    public string PangolinEndpoint { get; set; } = "https://app.pangolin.net";
    public string? NewtId { get; set; }
    public string? NewtSecret { get; set; }

    // NFS volume settings
    public string? NfsServer { get; set; }
    /// <summary>NFS export path on the server (e.g. "/srv/nfs" or "/export/data").</summary>
    public string? NfsExport { get; set; }
    /// <summary>Optional subfolder within the export (e.g. "ots_cms"). Empty/null = export root.</summary>
    public string? NfsExportFolder { get; set; }
    /// <summary>Additional NFS mount options appended after the defaults (nfsvers=4,proto=tcp).</summary>
    public string? NfsExtraOptions { get; set; }
}
