using Books.Api.Domain.UserAccess;
using Dapper;
using Npgsql;

namespace Books.Api.EventFlow.Repositories;

public class UserCompanyAccessRepository(NpgsqlDataSource dataSource) : IUserCompanyAccessRepository
{
    public async Task<IReadOnlyList<UserCompanyAccessDto>> GetByUserIdAsync(
        string userId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        const string sql = """
            SELECT
                aggregate_id AS Id,
                user_id AS UserId,
                company_id AS CompanyId,
                role AS RoleString,
                granted_by AS GrantedBy,
                granted_at AS GrantedAt,
                is_active AS IsActive,
                revoked_at AS RevokedAt,
                revoked_by AS RevokedBy
            FROM user_company_access_read_models
            WHERE user_id = @UserId
              AND is_active = true
            ORDER BY granted_at DESC
            """;

        var rows = await connection.QueryAsync<UserCompanyAccessRow>(sql, new { UserId = userId });
        return rows.Select(r => r.ToDto()).ToList();
    }

    public async Task<IReadOnlyList<UserCompanyAccessDto>> GetByCompanyIdAsync(
        string companyId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        const string sql = """
            SELECT
                aggregate_id AS Id,
                user_id AS UserId,
                company_id AS CompanyId,
                role AS RoleString,
                granted_by AS GrantedBy,
                granted_at AS GrantedAt,
                is_active AS IsActive,
                revoked_at AS RevokedAt,
                revoked_by AS RevokedBy
            FROM user_company_access_read_models
            WHERE company_id = @CompanyId
              AND is_active = true
            ORDER BY
                CASE role
                    WHEN 'owner' THEN 1
                    WHEN 'accountant' THEN 2
                    WHEN 'viewer' THEN 3
                END,
                granted_at ASC
            """;

        var rows = await connection.QueryAsync<UserCompanyAccessRow>(sql, new { CompanyId = companyId });
        return rows.Select(r => r.ToDto()).ToList();
    }

    public async Task<UserCompanyAccessDto?> GetAccessAsync(
        string userId,
        string companyId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        const string sql = """
            SELECT
                aggregate_id AS Id,
                user_id AS UserId,
                company_id AS CompanyId,
                role AS RoleString,
                granted_by AS GrantedBy,
                granted_at AS GrantedAt,
                is_active AS IsActive,
                revoked_at AS RevokedAt,
                revoked_by AS RevokedBy
            FROM user_company_access_read_models
            WHERE user_id = @UserId
              AND company_id = @CompanyId
              AND is_active = true
            """;

        var row = await connection.QuerySingleOrDefaultAsync<UserCompanyAccessRow>(
            sql,
            new { UserId = userId, CompanyId = companyId });

        return row?.ToDto();
    }

    public async Task<bool> HasAccessAsync(
        string userId,
        string companyId,
        CompanyRole minimumRole,
        CancellationToken cancellationToken = default)
    {
        var access = await GetAccessAsync(userId, companyId, cancellationToken);
        if (access == null) return false;

        // Role hierarchy: Owner > Accountant > Viewer
        return minimumRole switch
        {
            CompanyRole.Viewer => true, // Any role can do viewer actions
            CompanyRole.Accountant => access.Role is CompanyRole.Owner or CompanyRole.Accountant,
            CompanyRole.Owner => access.Role is CompanyRole.Owner,
            _ => false
        };
    }

    public async Task<UserCompanyAccessDto?> GetByIdAsync(
        string accessId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        const string sql = """
            SELECT
                aggregate_id AS Id,
                user_id AS UserId,
                company_id AS CompanyId,
                role AS RoleString,
                granted_by AS GrantedBy,
                granted_at AS GrantedAt,
                is_active AS IsActive,
                revoked_at AS RevokedAt,
                revoked_by AS RevokedBy
            FROM user_company_access_read_models
            WHERE aggregate_id = @AccessId
            """;

        var row = await connection.QuerySingleOrDefaultAsync<UserCompanyAccessRow>(
            sql,
            new { AccessId = accessId });

        return row?.ToDto();
    }

    /// <summary>
    /// Internal row type for Dapper mapping with string role.
    /// </summary>
    private sealed class UserCompanyAccessRow
    {
        public string Id { get; init; } = string.Empty;
        public string UserId { get; init; } = string.Empty;
        public string CompanyId { get; init; } = string.Empty;
        public string RoleString { get; init; } = string.Empty;
        public string GrantedBy { get; init; } = string.Empty;
        public DateTimeOffset GrantedAt { get; init; }
        public bool IsActive { get; init; }
        public DateTimeOffset? RevokedAt { get; init; }
        public string? RevokedBy { get; init; }

        public UserCompanyAccessDto ToDto() => new(
            Id,
            UserId,
            CompanyId,
            CompanyRoleExtensions.FromDbString(RoleString),
            GrantedBy,
            GrantedAt,
            IsActive,
            RevokedAt,
            RevokedBy);
    }
}
