using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using OTSSignsOrchestrator.Core.Configuration;

namespace OTSSignsOrchestrator.Core.Services;

/// <summary>
/// Provides connectivity testing and administrative operations against deployed Xibo CMS instances.
///
/// Bootstrap flow:
///   1. After a new instance is deployed, <see cref="PostInstanceInitService"/> calls
///      <see cref="LoginAsync"/> with the default Xibo admin credentials to obtain a session cookie.
///   2. Subsequent operations (create user, register OAuth2 app, set theme) authenticate
///      using that session cookie — no pre-existing OAuth2 application is required.
/// </summary>
public class XiboApiService
{
    private readonly IHttpClientFactory _httpClientFactory;
    private readonly XiboOptions _options;
    private readonly ILogger<XiboApiService> _logger;

    public XiboApiService(
        IHttpClientFactory httpClientFactory,
        IOptions<XiboOptions> options,
        ILogger<XiboApiService> logger)
    {
        _httpClientFactory = httpClientFactory;
        _options = options.Value;
        _logger = logger;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Connection test
    // ─────────────────────────────────────────────────────────────────────────

    public async Task<XiboTestResult> TestConnectionAsync(string instanceUrl, string clientId, string clientSecret)
    {
        _logger.LogInformation("Testing Xibo connection to {InstanceUrl}", instanceUrl);

        var client = _httpClientFactory.CreateClient("XiboApi");
        client.Timeout = TimeSpan.FromSeconds(_options.TestConnectionTimeoutSeconds);

        try
        {
            var token = await GetTokenAsync(instanceUrl, clientId, clientSecret, client);
            _logger.LogInformation("Xibo connection test succeeded for {InstanceUrl}", instanceUrl);
            return new XiboTestResult
            {
                IsValid   = true,
                Message   = "Connected successfully.",
                HttpStatus = 200
            };
        }
        catch (XiboAuthException ex)
        {
            return new XiboTestResult
            {
                IsValid    = false,
                Message    = ex.Message,
                HttpStatus = ex.HttpStatus
            };
        }
        catch (TaskCanceledException)
        {
            return new XiboTestResult { IsValid = false, Message = "Connection timed out." };
        }
        catch (HttpRequestException ex)
        {
            return new XiboTestResult { IsValid = false, Message = $"Cannot reach Xibo instance: {ex.Message}" };
        }
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Session login
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Obtains a Bearer access token using the OAuth2 <c>client_credentials</c> grant.
    /// The caller must have previously created an OAuth2 application in the Xibo CMS
    /// admin UI and provide the resulting <paramref name="clientId"/> and
    /// <paramref name="clientSecret"/>.
    /// </summary>
    public async Task<string> LoginAsync(string instanceUrl, string clientId, string clientSecret)
    {
        var baseUrl  = instanceUrl.TrimEnd('/');
        var tokenUrl = $"{baseUrl}/api/authorize/access_token";
        var client   = _httpClientFactory.CreateClient("XiboApi");
        client.Timeout = TimeSpan.FromSeconds(30);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type",    "client_credentials"),
            new KeyValuePair<string, string>("client_id",     clientId),
            new KeyValuePair<string, string>("client_secret", clientSecret),
        });

        var response = await client.PostAsync(tokenUrl, form);

        if (!response.IsSuccessStatusCode)
        {
            var body = await response.Content.ReadAsStringAsync();
            throw new XiboAuthException(
                $"Xibo client_credentials login failed for client '{clientId}': HTTP {(int)response.StatusCode} — {body}",
                (int)response.StatusCode);
        }

        using var doc   = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        var accessToken = doc.RootElement.GetProperty("access_token").GetString()
                          ?? throw new InvalidOperationException("access_token missing in Xibo token response.");

        _logger.LogInformation("Xibo access token obtained for client '{ClientId}' at {Url}", clientId, baseUrl);
        return accessToken;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Health / readiness
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Polls <paramref name="instanceUrl"/> until Xibo is genuinely online by calling
    /// the public <c>/about</c> page (no auth required) and confirming the response body
    /// contains the word "Xibo". <paramref name="instanceUrl"/> must already include the
    /// instance sub-path (e.g. <c>https://ots.ots-signs.com/ots</c>).
    /// The JSON <c>/api/about</c> and <c>/api/clock</c> endpoints both require auth, so
    /// the HTML about page is the only reliable unauthenticated Xibo-specific probe.
    /// A plain 200 from a proxy is not sufficient — the body must contain "Xibo".
    /// </summary>
    public async Task<bool> WaitForReadyAsync(
        string instanceUrl,
        TimeSpan timeout,
        CancellationToken ct = default)
    {
        var deadline  = DateTime.UtcNow + timeout;
        var baseUrl   = instanceUrl.TrimEnd('/');
        var client    = _httpClientFactory.CreateClient("XiboHealth");
        client.Timeout = TimeSpan.FromSeconds(10);
        var healthUrl = $"{baseUrl}/about";

        _logger.LogInformation("Waiting for Xibo instance to become ready: {Url}", healthUrl);

        while (DateTime.UtcNow < deadline && !ct.IsCancellationRequested)
        {
            try
            {
                var response = await client.GetAsync(healthUrl, ct);
                if (response.IsSuccessStatusCode)
                {
                    // The public /about page always contains the word "Xibo" in its HTML
                    // when Xibo itself is serving responses. A proxy 200 page will not.
                    var body = await response.Content.ReadAsStringAsync(ct);
                    if (body.Contains("Xibo", StringComparison.OrdinalIgnoreCase))
                    {
                        _logger.LogInformation("Xibo is ready: {Url}", healthUrl);
                        return true;
                    }
                    _logger.LogDebug("About page returned 200 but body lacks 'Xibo' — proxy may be up but Xibo not yet ready");
                }
            }
            catch { /* not yet available */ }

            await Task.Delay(TimeSpan.FromSeconds(10), ct);
        }

        _logger.LogWarning("Xibo did not become ready within {Timeout}: {Url}", timeout, healthUrl);
        return false;
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Admin user
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Creates a new super-admin user in the Xibo instance and returns its numeric ID.
    /// Authenticates using the supplied <paramref name="accessToken"/> (Bearer token
    /// obtained from <see cref="LoginAsync"/>).
    /// </summary>
    public async Task<int> CreateAdminUserAsync(
        string instanceUrl,
        string accessToken,
        string newUsername,
        string newPassword,
        string email,
        int groupId)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("userName",                 newUsername),
            new KeyValuePair<string, string>("email",                    email),
            new KeyValuePair<string, string>("userTypeId",               "1"),          // Super Admin
            new KeyValuePair<string, string>("homePageId",               "icondashboard.view"),
            new KeyValuePair<string, string>("libraryQuota",             "0"),
            new KeyValuePair<string, string>("groupId",                  groupId.ToString()),
            new KeyValuePair<string, string>("password",                 newPassword),
            new KeyValuePair<string, string>("newUserWizard",            "0"),
            new KeyValuePair<string, string>("hideNavigation",           "0"),
            new KeyValuePair<string, string>("isPasswordChangeRequired", "0"),
        });

        var response = await client.PostAsync($"{baseUrl}/api/user", form);
        await EnsureSuccessAsync(response, "create Xibo admin user");

        using var doc   = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        var       userId = doc.RootElement.GetProperty("userId").GetInt32();

        _logger.LogInformation("Xibo admin user created: username={Username}, userId={UserId}", newUsername, userId);
        return userId;
    }

    /// <summary>
    /// Changes the password of an existing Xibo user.
    /// Authenticates using the supplied <paramref name="accessToken"/> (Bearer token
    /// obtained from <see cref="LoginAsync"/>).
    /// </summary>
    public async Task RotateUserPasswordAsync(
        string instanceUrl,
        string accessToken,
        int userId,
        string newPassword)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("newUserPassword",       newPassword),
            new KeyValuePair<string, string>("retypeNewUserPassword", newPassword),
        });

        var response = await client.PutAsync($"{baseUrl}/api/user/{userId}", form);
        await EnsureSuccessAsync(response, "rotate Xibo user password");

        _logger.LogInformation("Xibo user password rotated: userId={UserId}", userId);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // OAuth2 application
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Registers a new client_credentials OAuth2 application in Xibo and returns
    /// the generated client_id and client_secret.
    /// Authenticates using the supplied <paramref name="accessToken"/> (Bearer token
    /// obtained from <see cref="LoginAsync"/>).
    /// </summary>
    public async Task<(string ClientId, string ClientSecret)> RegisterOAuthClientAsync(
        string instanceUrl,
        string accessToken,
        string appName)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("name",         appName),
            new KeyValuePair<string, string>("clientId",     Guid.NewGuid().ToString("N")),
            new KeyValuePair<string, string>("confidential", "1"),
            new KeyValuePair<string, string>("authCode",     "0"),
            new KeyValuePair<string, string>("clientCredentials", "1"),
        });

        var response = await client.PostAsync($"{baseUrl}/api/application", form);
        await EnsureSuccessAsync(response, "register Xibo OAuth2 application");

        using var doc    = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        var       root   = doc.RootElement;
        var       cid    = root.GetProperty("key").GetString()
                           ?? throw new InvalidOperationException("Xibo application 'key' missing in response.");
        var       secret = root.GetProperty("secret").GetString()
                           ?? throw new InvalidOperationException("Xibo application 'secret' missing in response.");

        _logger.LogInformation("Xibo OAuth2 application registered: name={Name}, clientId={ClientId}", appName, cid);
        return (cid, secret);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // Theme
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Sets the active CMS theme by writing the THEME_FOLDER setting.
    /// Authenticates using the supplied <paramref name="accessToken"/> (Bearer token
    /// obtained from <see cref="LoginAsync"/>).
    /// </summary>
    public async Task SetThemeAsync(
        string instanceUrl,
        string accessToken,
        string themeFolderName = "otssigns")
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        // Xibo stores settings as an array: settings[THEME_FOLDER]=otssigns
        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("settings[THEME_FOLDER]", themeFolderName),
        });

        var response = await client.PostAsync($"{baseUrl}/api/admin/setting", form);
        await EnsureSuccessAsync(response, "set Xibo theme");

        _logger.LogInformation("Xibo theme set to: {Theme}", themeFolderName);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // User groups
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Creates a new user group and returns its numeric group ID.
    /// </summary>
    public async Task<int> CreateUserGroupAsync(
        string instanceUrl,
        string accessToken,
        string groupName)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("group",         groupName),
            new KeyValuePair<string, string>("libraryQuota",  "0"),
        });

        var response = await client.PostAsync($"{baseUrl}/api/group", form);
        await EnsureSuccessAsync(response, "create Xibo user group");

        // The response is an array containing the created group
        using var doc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        var root = doc.RootElement;
        // Response may be an array or a single object depending on Xibo version
        var groupEl = root.ValueKind == JsonValueKind.Array ? root[0] : root;
        var gid = groupEl.GetProperty("groupId").GetInt32();

        _logger.LogInformation("Xibo user group created: name={Name}, groupId={GroupId}", groupName, gid);
        return gid;
    }

    /// <summary>
    /// Assigns a user to a Xibo user group.
    /// </summary>
    public async Task AssignUserToGroupAsync(
        string instanceUrl,
        string accessToken,
        int groupId,
        int userId)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("userId[]", userId.ToString()),
        });

        var response = await client.PostAsync($"{baseUrl}/api/group/members/assign/{groupId}", form);
        await EnsureSuccessAsync(response, $"assign user {userId} to group {groupId}");

        _logger.LogInformation("User {UserId} assigned to group {GroupId}", userId, groupId);
    }

    // ─────────────────────────────────────────────────────────────────────────
    // User lookup / update / deletion
    // ─────────────────────────────────────────────────────────────────────────

    /// <summary>
    /// Updates an existing Xibo user's username, password, and email.
    /// Authenticates using the supplied <paramref name="accessToken"/>.
    /// </summary>
    public async Task UpdateUserAsync(
        string instanceUrl,
        string accessToken,
        int    userId,
        string newUsername,
        string newPassword,
        string email)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("userName",                 newUsername),
            new KeyValuePair<string, string>("email",                    email),
            new KeyValuePair<string, string>("userTypeId",               "1"),
            new KeyValuePair<string, string>("homePageId",               "icondashboard.view"),
            new KeyValuePair<string, string>("libraryQuota",             "0"),
            new KeyValuePair<string, string>("newPassword",              newPassword),
            new KeyValuePair<string, string>("retypeNewPassword",        newPassword),
            new KeyValuePair<string, string>("newUserWizard",            "0"),
            new KeyValuePair<string, string>("hideNavigation",           "0"),
            new KeyValuePair<string, string>("isPasswordChangeRequired", "0"),
        });

        var response = await client.PutAsync($"{baseUrl}/api/user/{userId}", form);
        await EnsureSuccessAsync(response, $"update Xibo user {userId}");

        _logger.LogInformation("Xibo user updated: userId={UserId}, newUsername={Username}", userId, newUsername);
    }

    /// <summary>
    /// Finds a Xibo user by username and returns their numeric user ID.
    /// Authenticates using the supplied <paramref name="accessToken"/>.
    /// </summary>
    public async Task<int> GetUserIdByNameAsync(string instanceUrl, string accessToken, string username)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var response = await client.GetAsync($"{baseUrl}/api/user?userName={Uri.EscapeDataString(username)}");
        await EnsureSuccessAsync(response, "look up Xibo user by name");

        using var doc = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        foreach (var user in doc.RootElement.EnumerateArray())
        {
            var name = user.GetProperty("userName").GetString();
            if (string.Equals(name, username, StringComparison.OrdinalIgnoreCase))
                return user.GetProperty("userId").GetInt32();
        }

        throw new InvalidOperationException(
            $"Xibo user '{username}' not found.");
    }

    /// <summary>
    /// Deletes a Xibo user by their numeric user ID.
    /// Authenticates using the supplied <paramref name="accessToken"/>.
    /// </summary>
    public async Task DeleteUserAsync(string instanceUrl, string accessToken, int userId)
    {
        var client  = _httpClientFactory.CreateClient("XiboApi");
        var baseUrl = instanceUrl.TrimEnd('/');

        SetBearer(client, accessToken);

        var response = await client.DeleteAsync($"{baseUrl}/api/user/{userId}");
        await EnsureSuccessAsync(response, $"delete Xibo user {userId}");

        _logger.LogInformation("Xibo user deleted: userId={UserId}", userId);
    }

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

    private async Task<string> GetTokenAsync(
        string baseUrl,
        string clientId,
        string clientSecret,
        HttpClient client)
    {
        var tokenUrl = $"{baseUrl}/api/authorize/access_token";
        var form = new FormUrlEncodedContent(new[]
        {
            new KeyValuePair<string, string>("grant_type",    "client_credentials"),
            new KeyValuePair<string, string>("client_id",     clientId),
            new KeyValuePair<string, string>("client_secret", clientSecret),
        });

        var response = await client.PostAsync(tokenUrl, form);

        if (!response.IsSuccessStatusCode)
        {
            throw new XiboAuthException(
                response.StatusCode switch
                {
                    System.Net.HttpStatusCode.Unauthorized    => "Invalid Xibo credentials.",
                    System.Net.HttpStatusCode.Forbidden       => "User lacks API permissions.",
                    System.Net.HttpStatusCode.ServiceUnavailable => "Xibo instance not ready.",
                    _ => $"Unexpected response: {(int)response.StatusCode}"
                },
                (int)response.StatusCode);
        }

        using var doc   = await JsonDocument.ParseAsync(await response.Content.ReadAsStreamAsync());
        var      aToken = doc.RootElement.GetProperty("access_token").GetString()
                          ?? throw new InvalidOperationException("access_token missing in Xibo token response.");
        return aToken;
    }

    private static void SetBearer(HttpClient client, string token)
        => client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);



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

// ─────────────────────────────────────────────────────────────────────────────
// Result / exception types
// ─────────────────────────────────────────────────────────────────────────────

public class XiboTestResult
{
    public bool   IsValid   { get; set; }
    public string Message   { get; set; } = string.Empty;
    public int    HttpStatus { get; set; }
}

public class XiboAuthException : Exception
{
    public int HttpStatus { get; }
    public XiboAuthException(string message, int httpStatus) : base(message)
        => HttpStatus = httpStatus;
}

