using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Stores and retrieves secrets from Bitwarden Secrets Manager (machine account API).
///
/// Configuration required in Settings:
///   Bitwarden.IdentityUrl   – defaults to https://identity.bitwarden.com
///   Bitwarden.ApiUrl        – defaults to https://api.bitwarden.com
///   Bitwarden.AccessToken   – machine account access token (sensitive)
///   Bitwarden.OrganizationId – Bitwarden organisation that owns the project
///   Bitwarden.ProjectId     – project where new secrets are created
/// </summary>
public class BitwardenSecretService : IBitwardenSecretService
{
    private readonly IHttpClientFactory _http;
    private readonly SettingsService _settings;
    private readonly ILogger<BitwardenSecretService> _logger;

    // Cached bearer token (refreshed per service lifetime — transient registration is assumed)
    private string? _bearerToken;

    public BitwardenSecretService(
        IHttpClientFactory http,
        SettingsService settings,
        ILogger<BitwardenSecretService> logger)
    {
        _http = http;
        _settings = settings;
        _logger = logger;
    }

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

    public async Task<bool> IsConfiguredAsync()
    {
        var token = await _settings.GetAsync(SettingsService.BitwardenAccessToken);
        var orgId = await _settings.GetAsync(SettingsService.BitwardenOrganizationId);
        return !string.IsNullOrWhiteSpace(token) && !string.IsNullOrWhiteSpace(orgId);
    }

    public async Task<string> CreateSecretAsync(string key, string value, string note = "")
    {
        var bearer    = await GetBearerTokenAsync();
        var projectId = await _settings.GetAsync(SettingsService.BitwardenProjectId);
        var orgId     = await _settings.GetAsync(SettingsService.BitwardenOrganizationId)
                        ?? throw new InvalidOperationException("Bitwarden OrganizationId is not configured.");
        var apiUrl    = await GetApiUrlAsync();

        var client = CreateClient(bearer);
        var body = new
        {
            organizationId = orgId,
            projectIds     = projectId is not null ? new[] { projectId } : Array.Empty<string>(),
            key            = key,
            value          = value,
            note           = note
        };

        var response = await client.PostAsJsonAsync($"{apiUrl}/secrets", body);
        await EnsureSuccessAsync(response, "create secret");

        var result = await response.Content.ReadFromJsonAsync<BwsSecretResponse>()
                     ?? throw new InvalidOperationException("Empty response from Bitwarden API.");

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

    public async Task<BitwardenSecret> GetSecretAsync(string secretId)
    {
        var bearer = await GetBearerTokenAsync();
        var apiUrl = await GetApiUrlAsync();
        var client = CreateClient(bearer);

        var response = await client.GetAsync($"{apiUrl}/secrets/{secretId}");
        await EnsureSuccessAsync(response, "get secret");

        var result = await response.Content.ReadFromJsonAsync<BwsSecretResponse>()
                     ?? throw new InvalidOperationException("Empty response from Bitwarden API.");

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

    public async Task UpdateSecretAsync(string secretId, string key, string value, string note = "")
    {
        var bearer    = await GetBearerTokenAsync();
        var projectId = await _settings.GetAsync(SettingsService.BitwardenProjectId);
        var orgId     = await _settings.GetAsync(SettingsService.BitwardenOrganizationId)
                        ?? throw new InvalidOperationException("Bitwarden OrganizationId is not configured.");
        var apiUrl    = await GetApiUrlAsync();

        var client = CreateClient(bearer);
        var body = new
        {
            organizationId = orgId,
            projectIds     = projectId is not null ? new[] { projectId } : Array.Empty<string>(),
            key            = key,
            value          = value,
            note           = note
        };

        var response = await client.PutAsJsonAsync($"{apiUrl}/secrets/{secretId}", body);
        await EnsureSuccessAsync(response, "update secret");

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

    public async Task<List<BitwardenSecretSummary>> ListSecretsAsync()
    {
        var bearer = await GetBearerTokenAsync();
        var orgId  = await _settings.GetAsync(SettingsService.BitwardenOrganizationId)
                     ?? throw new InvalidOperationException("Bitwarden OrganizationId is not configured.");
        var apiUrl = await GetApiUrlAsync();
        var client = CreateClient(bearer);

        var response = await client.GetAsync($"{apiUrl}/organizations/{orgId}/secrets");
        await EnsureSuccessAsync(response, "list secrets");

        var result = await response.Content.ReadFromJsonAsync<BwsSecretsListResponse>();

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

    // ─────────────────────────────────────────────────────────────────────────
    // Auth
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Exchanges the machine-account access token for a short-lived Bearer token.
    /// The access token format is:  0.{tokenId}.{clientSecret}:{encKeyB64}
    /// The client_id used is "machine.{tokenId}".
    /// </summary>
    private async Task<string> GetBearerTokenAsync()
    {
        if (_bearerToken is not null)
            return _bearerToken;

        var rawToken    = await _settings.GetAsync(SettingsService.BitwardenAccessToken)
                          ?? throw new InvalidOperationException("Bitwarden AccessToken is not configured in Settings.");
        var identityUrl = await GetIdentityUrlAsync();

        // Parse token: "0.<tokenId>.<clientSecretAndKey>" — split off the first two segments
        var parts = rawToken.Split('.', 3);
        if (parts.Length < 3)
            throw new FormatException(
                "Bitwarden access token has unexpected format. Expected: 0.<tokenId>.<rest>");

        var tokenId      = parts[1];
        var clientSecret = parts[2]; // may contain ":base64key" suffix — include all of it

        var client = _http.CreateClient("Bitwarden");
        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type",    "client_credentials"),
            new KeyValuePair<string, string>("client_id",     $"machine.{tokenId}"),
            new KeyValuePair<string, string>("client_secret", clientSecret),
            new KeyValuePair<string, string>("scope",         "api.secrets"),
        });

        var response    = await client.PostAsync($"{identityUrl}/connect/token", form);
        await EnsureSuccessAsync(response, "authenticate with Bitwarden identity");

        var token = await response.Content.ReadFromJsonAsync<BwsTokenResponse>()
                    ?? throw new InvalidOperationException("Empty token response from Bitwarden.");

        _bearerToken = token.AccessToken;
        _logger.LogInformation("Bitwarden bearer token acquired.");
        return _bearerToken;
    }

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

    private async Task<string> GetIdentityUrlAsync()
        => (await _settings.GetAsync(SettingsService.BitwardenIdentityUrl, "https://identity.bitwarden.com")).TrimEnd('/');

    private async Task<string> GetApiUrlAsync()
        => (await _settings.GetAsync(SettingsService.BitwardenApiUrl, "https://api.bitwarden.com")).TrimEnd('/');

    private static HttpClient CreateClient(string bearerToken)
    {
        var client = new HttpClient();
        client.DefaultRequestHeaders.Authorization =
            new AuthenticationHeaderValue("Bearer", bearerToken);
        return client;
    }

    private static async Task EnsureSuccessAsync(HttpResponseMessage response, string operation)
    {
        if (!response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync();
            throw new HttpRequestException(
                $"Bitwarden API call '{operation}' failed: {(int)response.StatusCode} {response.ReasonPhrase} — {body}");
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Internal DTOs
    // ─────────────────────────────────────────────────────────────────────────

    private sealed class BwsTokenResponse
    {
        [JsonPropertyName("access_token")]
        public string AccessToken { get; set; } = string.Empty;
    }

    private sealed class BwsSecretResponse
    {
        [JsonPropertyName("id")]
        public string Id { get; set; } = string.Empty;

        [JsonPropertyName("organizationId")]
        public string OrganizationId { get; set; } = string.Empty;

        [JsonPropertyName("key")]
        public string Key { get; set; } = string.Empty;

        [JsonPropertyName("value")]
        public string Value { get; set; } = string.Empty;

        [JsonPropertyName("note")]
        public string? Note { get; set; }

        [JsonPropertyName("creationDate")]
        public DateTime CreationDate { get; set; }
    }

    private sealed class BwsSecretsListResponse
    {
        [JsonPropertyName("data")]
        public List<BwsSecretResponse>? Data { get; set; }
    }
}
