using Bitwarden.Sdk;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OTSSignsOrchestrator.Core.Configuration;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Stores and retrieves secrets from Bitwarden Secrets Manager via the official Bitwarden C# SDK.
///
/// Configuration is read from <see cref="BitwardenOptions"/> (bound to appsettings.json → "Bitwarden").
///
/// The SDK state file is persisted to %APPDATA%/OTSSignsOrchestrator/bitwarden.state
/// so the SDK can cache its internal state across restarts.
/// </summary>
public class BitwardenSecretService : IBitwardenSecretService, IDisposable
{
    private readonly IOptionsMonitor<BitwardenOptions> _optionsMonitor;
    private readonly ILogger<BitwardenSecretService> _logger;

    // Lazily created on first use (per service instance — registered as Transient).
    private BitwardenClient? _client;
    private string? _clientAccessToken; // track which token the client was created with
    private bool _disposed;

    /// <summary>Always returns the latest config snapshot (reloaded when appsettings.json changes).</summary>
    private BitwardenOptions Options => _optionsMonitor.CurrentValue;

    public BitwardenSecretService(
        IOptionsMonitor<BitwardenOptions> optionsMonitor,
        ILogger<BitwardenSecretService> logger)
    {
        _optionsMonitor = optionsMonitor;
        _logger         = logger;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // IBitwardenSecretService
    // ─────────────────────────────────────────────────────────────────────────

    public Task<bool> IsConfiguredAsync()
    {
        var opts = Options;
        var configured = !string.IsNullOrWhiteSpace(opts.AccessToken)
                      && !string.IsNullOrWhiteSpace(opts.OrganizationId);
        return Task.FromResult(configured);
    }

    public async Task<string> CreateSecretAsync(string key, string value, string note = "")
    {
        var client     = await GetClientAsync();
        var orgId      = GetOrgId();
        var projectIds = GetProjectIds();

        var result = await Task.Run(() => client.Secrets.Create(orgId, key, value, note, projectIds));

        _logger.LogInformation("Bitwarden secret created: key={Key}, id={Id}", key, result.Id);
        return result.Id.ToString();
    }

    public async Task<string> CreateInstanceSecretAsync(string key, string value, string note = "")
    {
        var client     = await GetClientAsync();
        var orgId      = GetOrgId();
        var projectIds = GetInstanceProjectIds();

        var result = await Task.Run(() => client.Secrets.Create(orgId, key, value, note, projectIds));

        _logger.LogInformation("Bitwarden instance secret created: key={Key}, id={Id}, project={Project}",
            key, result.Id, Options.InstanceProjectId);
        return result.Id.ToString();
    }

    public async Task<BitwardenSecret> GetSecretAsync(string secretId)
    {
        var client = await GetClientAsync();
        var result = await Task.Run(() => client.Secrets.Get(Guid.Parse(secretId)));

        return new BitwardenSecret
        {
            Id           = result.Id.ToString(),
            Key          = result.Key,
            Value        = result.Value,
            Note         = result.Note ?? string.Empty,
            CreationDate = result.CreationDate.DateTime
        };
    }

    public async Task UpdateSecretAsync(string secretId, string key, string value, string note = "")
    {
        var client     = await GetClientAsync();
        var orgId      = GetOrgId();
        var projectIds = GetProjectIds();

        await Task.Run(() => client.Secrets.Update(orgId, Guid.Parse(secretId), key, value, note, projectIds));

        _logger.LogInformation("Bitwarden secret updated: key={Key}, id={Id}", key, secretId);
    }

    public async Task UpdateInstanceSecretAsync(string secretId, string key, string value, string note = "")
    {
        var client     = await GetClientAsync();
        var orgId      = GetOrgId();
        var projectIds = GetInstanceProjectIds();

        await Task.Run(() => client.Secrets.Update(orgId, Guid.Parse(secretId), key, value, note, projectIds));

        _logger.LogInformation("Bitwarden instance secret updated: key={Key}, id={Id}, project={Project}",
            key, secretId, Options.InstanceProjectId);
    }

    public async Task<List<BitwardenSecretSummary>> ListSecretsAsync()
    {
        var client = await GetClientAsync();
        var orgId  = GetOrgId();
        var result = await Task.Run(() => client.Secrets.List(orgId));

        return result.Data?.Select(s => new BitwardenSecretSummary
        {
            Id           = s.Id.ToString(),
            Key          = s.Key,
            CreationDate = DateTime.MinValue
        }).ToList() ?? new List<BitwardenSecretSummary>();
    }

    // ─────────────────────────────────────────────────────────────────────────
    // SDK client initialisation
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Returns an authenticated <see cref="BitwardenClient"/>, creating and logging in on first use.
    /// </summary>
    private async Task<BitwardenClient> GetClientAsync()
    {
        var opts = Options;

        // If credentials changed since the client was created, tear it down so we re-auth
        if (_client is not null && _clientAccessToken != opts.AccessToken)
        {
            _logger.LogInformation("Bitwarden credentials changed — recreating SDK client");
            _client.Dispose();
            _client = null;
        }

        if (_client is not null)
            return _client;

        if (string.IsNullOrWhiteSpace(opts.AccessToken))
            throw new InvalidOperationException("Bitwarden AccessToken is not configured. Set it in Settings → Bitwarden.");

        var accessToken = opts.AccessToken;
        var apiUrl      = (opts.ApiUrl ?? "https://api.bitwarden.com").TrimEnd('/');
        var identityUrl = (opts.IdentityUrl ?? "https://identity.bitwarden.com").TrimEnd('/');

        var sdkSettings = new BitwardenSettings { ApiUrl = apiUrl, IdentityUrl = identityUrl };
        var client      = new BitwardenClient(sdkSettings);

        await Task.Run(() => client.Auth.LoginAccessToken(accessToken, GetStateFilePath()));

        _logger.LogInformation("Bitwarden SDK client initialised and authenticated.");
        _client = client;
        _clientAccessToken = accessToken;
        return _client;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Helpers
    // ─────────────────────────────────────────────────────────────────────────

    private Guid GetOrgId()
    {
        var orgId = Options.OrganizationId;
        if (string.IsNullOrWhiteSpace(orgId))
            throw new InvalidOperationException("Bitwarden OrganizationId is not configured.");
        return Guid.Parse(orgId);
    }

    private Guid[] GetProjectIds()
    {
        var projectId = Options.ProjectId;
        if (string.IsNullOrWhiteSpace(projectId))
            throw new InvalidOperationException(
                "Bitwarden ProjectId is required. Set it in Settings → Bitwarden.");
        return new[] { Guid.Parse(projectId) };
    }

    /// <summary>
    /// Returns the project IDs array for instance-level secrets.
    /// Uses <see cref="BitwardenOptions.InstanceProjectId"/> when configured,
    /// otherwise falls back to the default <see cref="BitwardenOptions.ProjectId"/>.
    /// </summary>
    private Guid[] GetInstanceProjectIds()
    {
        var instanceProjectId = Options.InstanceProjectId;
        if (!string.IsNullOrWhiteSpace(instanceProjectId))
        {
            _logger.LogDebug("Using instance Bitwarden project: {ProjectId}", instanceProjectId);
            return new[] { Guid.Parse(instanceProjectId) };
        }

        // Fall back to the default config project
        return GetProjectIds();
    }

    /// <summary>
    /// Returns the path where the SDK stores its state between sessions.
    /// Stored under %APPDATA%/OTSSignsOrchestrator/bitwarden.state.
    /// </summary>
    private static string GetStateFilePath()
    {
        var dir = Path.Combine(
            Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
            "OTSSignsOrchestrator");
        Directory.CreateDirectory(dir);
        return Path.Combine(dir, "bitwarden.state");
    }

    // ─────────────────────────────────────────────────────────────────────────
    // IDisposable
    // ─────────────────────────────────────────────────────────────────────────

    public void Dispose()
    {
        if (!_disposed)
        {
            _client?.Dispose();
            _disposed = true;
        }
    }
}
