using System.Globalization;
using MySqlConnector;

namespace ApiDenuncias.Services;

public sealed class UserComplaintAccessService
{
    private readonly MySqlConnectionStringProvider _connectionStringProvider;

    public UserComplaintAccessService(MySqlConnectionStringProvider connectionStringProvider)
    {
        _connectionStringProvider = connectionStringProvider;
    }

    public async Task<HashSet<int>> GetAllowedComplaintIdsAsync(
        string username,
        CancellationToken cancellationToken = default)
    {
        var access = await GetComplaintAccessAsync(username, null, cancellationToken);
        return access.Keys.ToHashSet();
    }

    public async Task<IReadOnlyDictionary<int, ComplaintAccessInfo>> GetComplaintAccessAsync(
        string username,
        IReadOnlyCollection<int>? complaintIds = null,
        CancellationToken cancellationToken = default)
    {
        if (string.IsNullOrWhiteSpace(username))
        {
            return new Dictionary<int, ComplaintAccessInfo>();
        }

        var connectionString = await _connectionStringProvider.GetConnectionStringAsync(cancellationToken);
        await using var connection = new MySqlConnection(connectionString);
        await connection.OpenAsync(cancellationToken);

        await using var command = connection.CreateCommand();
        command.Parameters.AddWithValue("@username", username.Trim());

        var idFilter = string.Empty;
        if (complaintIds is { Count: > 0 })
        {
            var parameters = new List<string>(complaintIds.Count);
            var index = 0;
            foreach (var complaintId in complaintIds.Where(id => id > 0).Distinct())
            {
                var parameterName = $"@complaintId{index++}";
                parameters.Add(parameterName);
                command.Parameters.AddWithValue(parameterName, complaintId);
            }

            if (parameters.Count == 0)
            {
                return new Dictionary<int, ComplaintAccessInfo>();
            }

            idFilter = $"AND COALESCE(ir.imported_complaint_report_id, ir.progressive_id) IN ({string.Join(", ", parameters)})";
        }

        command.CommandText = $"""
            SELECT
                COALESCE(ir.imported_complaint_report_id, ir.progressive_id) AS complaint_id,
                owner.username AS owner_username,
                CASE WHEN ir.owner_user_id = viewer.id THEN 1 ELSE 0 END AS owned_by_current_user,
                CASE
                    WHEN ir.owner_user_id IS NULL THEN 0
                    WHEN EXISTS (
                        SELECT 1
                        FROM app_user_groups shared_membership
                        INNER JOIN work_groups active_group
                            ON active_group.id = shared_membership.work_group_id
                           AND active_group.is_active = 1
                        WHERE shared_membership.app_user_id IN (viewer.id, ir.owner_user_id)
                        GROUP BY shared_membership.work_group_id
                        HAVING COUNT(DISTINCT shared_membership.app_user_id) = 2
                    ) THEN 1
                    ELSE 0
                END AS accessible_by_group,
                (
                    SELECT GROUP_CONCAT(DISTINCT wg.code ORDER BY wg.code SEPARATOR ',')
                    FROM app_user_groups owner_membership
                    INNER JOIN work_groups wg
                        ON wg.id = owner_membership.work_group_id
                       AND wg.is_active = 1
                    WHERE owner_membership.app_user_id = ir.owner_user_id
                ) AS owner_group_codes
            FROM app_users viewer
            INNER JOIN inbox_reports ir
                ON COALESCE(ir.imported_complaint_report_id, ir.progressive_id) IS NOT NULL
            LEFT JOIN app_users owner ON owner.id = ir.owner_user_id
            LEFT JOIN user_inbox_reports current_tracking
                ON current_tracking.inbox_report_id = ir.id
               AND current_tracking.app_user_id = viewer.id
            WHERE viewer.username = @username
              {idFilter}
              AND (
                    ir.owner_user_id = viewer.id
                 OR EXISTS (
                        SELECT 1
                        FROM app_user_groups shared_membership
                        INNER JOIN work_groups active_group
                            ON active_group.id = shared_membership.work_group_id
                           AND active_group.is_active = 1
                        WHERE shared_membership.app_user_id IN (viewer.id, ir.owner_user_id)
                        GROUP BY shared_membership.work_group_id
                        HAVING COUNT(DISTINCT shared_membership.app_user_id) = 2
                    )
                 OR (
                        ir.owner_user_id IS NULL
                    AND current_tracking.download_count > 0
                    )
              );
            """;

        var result = new Dictionary<int, ComplaintAccessInfo>();
        await using var reader = await command.ExecuteReaderAsync(cancellationToken);
        while (await reader.ReadAsync(cancellationToken))
        {
            var complaintId = Convert.ToInt32(
                reader.GetValue(reader.GetOrdinal("complaint_id")),
                CultureInfo.InvariantCulture);
            var ownerUsername = reader.IsDBNull(reader.GetOrdinal("owner_username"))
                ? string.Empty
                : reader.GetString(reader.GetOrdinal("owner_username"));
            var ownedByCurrentUser =
                reader.GetInt32(reader.GetOrdinal("owned_by_current_user")) == 1;
            var accessibleByGroup =
                reader.GetInt32(reader.GetOrdinal("accessible_by_group")) == 1;
            var groupCodes = reader.IsDBNull(reader.GetOrdinal("owner_group_codes"))
                ? []
                : reader.GetString(reader.GetOrdinal("owner_group_codes"))
                    .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);

            result[complaintId] = new ComplaintAccessInfo(
                complaintId,
                ownerUsername,
                ownedByCurrentUser,
                accessibleByGroup,
                groupCodes);
        }

        return result;
    }

    public async Task<bool> CanAccessComplaintAsync(
        string username,
        int complaintId,
        CancellationToken cancellationToken = default)
    {
        if (complaintId <= 0)
        {
            return false;
        }

        var access = await GetComplaintAccessAsync(
            username,
            [complaintId],
            cancellationToken);
        return access.ContainsKey(complaintId);
    }
}

public sealed record ComplaintAccessInfo(
    int ComplaintId,
    string OwnerUsername,
    bool OwnedByCurrentUser,
    bool AccessibleByGroup,
    IReadOnlyList<string> OwnerGroupCodes)
{
    public bool RequiresOwnerConfirmation =>
        !OwnedByCurrentUser &&
        AccessibleByGroup &&
        !string.IsNullOrWhiteSpace(OwnerUsername);
}
