using Microsoft.AspNetCore.DataProtection;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using OTSSignsOrchestrator.Core.Data;
using OTSSignsOrchestrator.Core.Models.Entities;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Reads and writes typed application settings from the AppSetting table.
/// Sensitive values are encrypted/decrypted transparently via DataProtection.
/// </summary>
public class SettingsService
{
    private readonly XiboContext _db;
    private readonly IDataProtector _protector;
    private readonly ILogger<SettingsService> _logger;

    // ── Category constants ─────────────────────────────────────────────────
    public const string CatGit = "Git";
    public const string CatMySql = "MySql";
    public const string CatSmtp = "Smtp";
    public const string CatPangolin = "Pangolin";
    public const string CatNfs = "Nfs";
    public const string CatDefaults = "Defaults";

    // ── Key constants ──────────────────────────────────────────────────────
    // Git
    public const string GitRepoUrl = "Git.RepoUrl";
    public const string GitRepoPat = "Git.RepoPat";

    // MySQL Admin
    public const string MySqlHost = "MySql.Host";
    public const string MySqlPort = "MySql.Port";
    public const string MySqlAdminUser = "MySql.AdminUser";
    public const string MySqlAdminPassword = "MySql.AdminPassword";

    // SMTP
    public const string SmtpServer = "Smtp.Server";
    public const string SmtpPort = "Smtp.Port";
    public const string SmtpUsername = "Smtp.Username";
    public const string SmtpPassword = "Smtp.Password";
    public const string SmtpUseTls = "Smtp.UseTls";
    public const string SmtpUseStartTls = "Smtp.UseStartTls";
    public const string SmtpRewriteDomain = "Smtp.RewriteDomain";
    public const string SmtpHostname = "Smtp.Hostname";
    public const string SmtpFromLineOverride = "Smtp.FromLineOverride";

    // Pangolin
    public const string PangolinEndpoint = "Pangolin.Endpoint";

    // NFS
    public const string NfsServer = "Nfs.Server";
    public const string NfsExport = "Nfs.Export";
    public const string NfsExportFolder = "Nfs.ExportFolder";
    public const string NfsOptions = "Nfs.Options";

    // Instance Defaults
    public const string DefaultCmsImage = "Defaults.CmsImage";
    public const string DefaultNewtImage = "Defaults.NewtImage";
    public const string DefaultMemcachedImage = "Defaults.MemcachedImage";
    public const string DefaultQuickChartImage = "Defaults.QuickChartImage";
    public const string DefaultCmsServerNameTemplate = "Defaults.CmsServerNameTemplate";
    public const string DefaultThemeHostPath = "Defaults.ThemeHostPath";
    public const string DefaultMySqlDbTemplate = "Defaults.MySqlDbTemplate";
    public const string DefaultMySqlUserTemplate = "Defaults.MySqlUserTemplate";
    public const string DefaultPhpPostMaxSize = "Defaults.PhpPostMaxSize";
    public const string DefaultPhpUploadMaxFilesize = "Defaults.PhpUploadMaxFilesize";
    public const string DefaultPhpMaxExecutionTime = "Defaults.PhpMaxExecutionTime";

    // Bitwarden Secrets Manager
    public const string CatBitwarden = "Bitwarden";
    public const string BitwardenIdentityUrl    = "Bitwarden.IdentityUrl";
    public const string BitwardenApiUrl         = "Bitwarden.ApiUrl";
    public const string BitwardenAccessToken    = "Bitwarden.AccessToken";
    public const string BitwardenOrganizationId = "Bitwarden.OrganizationId";
    public const string BitwardenProjectId      = "Bitwarden.ProjectId";

    // Xibo bootstrap OAuth2 credentials (one-time global setup for orchestrator API access)
    public const string CatXibo = "Xibo";
    public const string XiboBootstrapClientId     = "Xibo.BootstrapClientId";
    public const string XiboBootstrapClientSecret = "Xibo.BootstrapClientSecret";

    // Instance-specific (keyed by abbreviation)
    /// <summary>
    /// Builds a per-instance settings key for the MySQL password.
    /// Stored encrypted via DataProtection so it can be retrieved on update/redeploy.
    /// </summary>
    public static string InstanceMySqlPassword(string abbrev)         => $"Instance.{abbrev}.MySqlPassword";
    /// <summary>Bitwarden secret ID for the instance's OTS admin password.</summary>
    public static string InstanceAdminPasswordSecretId(string abbrev) => $"Instance.{abbrev}.AdminPasswordBwsId";
    /// <summary>Bitwarden secret ID for the instance's Xibo OAuth2 client secret.</summary>
    public static string InstanceOAuthSecretId(string abbrev)         => $"Instance.{abbrev}.OAuthSecretBwsId";
    /// <summary>Xibo OAuth2 client_id generated for this instance's OTS application.</summary>
    public static string InstanceOAuthClientId(string abbrev)         => $"Instance.{abbrev}.OAuthClientId";
    public const string CatInstance = "Instance";

    public SettingsService(
        XiboContext db,
        IDataProtectionProvider dataProtection,
        ILogger<SettingsService> logger)
    {
        _db = db;
        _protector = dataProtection.CreateProtector("OTSSignsOrchestrator.Settings");
        _logger = logger;
    }

    /// <summary>Get a single setting value, decrypting if sensitive.</summary>
    public async Task<string?> GetAsync(string key)
    {
        var setting = await _db.AppSettings.FindAsync(key);
        if (setting == null) return null;
        return setting.IsSensitive && setting.Value != null
            ? Unprotect(setting.Value)
            : setting.Value;
    }

    /// <summary>Get a setting with a fallback default.</summary>
    public async Task<string> GetAsync(string key, string defaultValue)
        => await GetAsync(key) ?? defaultValue;

    /// <summary>Set a single setting, encrypting if sensitive.</summary>
    public async Task SetAsync(string key, string? value, string category, bool isSensitive = false)
    {
        var setting = await _db.AppSettings.FindAsync(key);
        if (setting == null)
        {
            setting = new AppSetting { Key = key, Category = category, IsSensitive = isSensitive };
            _db.AppSettings.Add(setting);
        }

        setting.Value = isSensitive && value != null ? _protector.Protect(value) : value;
        setting.IsSensitive = isSensitive;
        setting.Category = category;
        setting.UpdatedAt = DateTime.UtcNow;
    }

    /// <summary>Save multiple settings in a single transaction.</summary>
    public async Task SaveManyAsync(IEnumerable<(string Key, string? Value, string Category, bool IsSensitive)> settings)
    {
        foreach (var (key, value, category, isSensitive) in settings)
            await SetAsync(key, value, category, isSensitive);

        await _db.SaveChangesAsync();
        _logger.LogInformation("Saved {Count} setting(s)",
            settings is ICollection<(string, string?, string, bool)> c ? c.Count : -1);
    }

    /// <summary>Get all settings in a category (values decrypted).</summary>
    public async Task<Dictionary<string, string?>> GetCategoryAsync(string category)
    {
        var settings = await _db.AppSettings
            .Where(s => s.Category == category)
            .ToListAsync();

        return settings.ToDictionary(
            s => s.Key,
            s => s.IsSensitive && s.Value != null ? Unprotect(s.Value) : s.Value);
    }

    private string? Unprotect(string protectedValue)
    {
        try
        {
            return _protector.Unprotect(protectedValue);
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Failed to unprotect setting value — returning null");
            return null;
        }
    }
}
