package permissions

import (
	"context"
	"database/sql"
)

// Checker verifies user permissions against the database.
type Checker struct {
	db *sql.DB
}

// NewChecker creates a new Checker backed by the given database connection.
func NewChecker(db *sql.DB) *Checker {
	return &Checker{db: db}
}

// GetUserPermissions returns the effective (OR'd) permissions for a user in a server.
// It aggregates all permissions from the roles assigned to the user, plus the
// @everyone role for that server.
func (c *Checker) GetUserPermissions(ctx context.Context, serverID string, userID string) (int64, error) {
	rows, err := c.db.QueryContext(ctx, `
		SELECT r.permissions
		FROM roles r
		INNER JOIN member_roles mr ON mr.role_id = r.id
		WHERE mr.user_id = $1 AND mr.server_id = $2
	`, userID, serverID)
	if err != nil {
		return 0, err
	}
	defer rows.Close()

	var effective int64
	found := false
	for rows.Next() {
		var perm int64
		if err := rows.Scan(&perm); err != nil {
			return 0, err
		}
		effective |= perm
		found = true
	}
	if err := rows.Err(); err != nil {
		return 0, err
	}

	// Also include the @everyone role permissions for this server.
	var everyonePerm int64
	err = c.db.QueryRowContext(ctx, `
		SELECT permissions FROM roles
		WHERE server_id = $1 AND is_default = TRUE
		LIMIT 1
	`, serverID).Scan(&everyonePerm)
	if err != nil && err != sql.ErrNoRows {
		return 0, err
	}
	effective |= everyonePerm
	if everyonePerm != 0 {
		found = true
	}

	if !found {
		return 0, nil
	}
	return effective, nil
}

// CheckPermission reports whether the user has all the required permission bits
// in the given server. ADMINISTRATOR bypasses all checks.
func (c *Checker) CheckPermission(ctx context.Context, serverID string, userID string, required int64) (bool, error) {
	// Check if the user is the server owner.
	var ownerID string
	err := c.db.QueryRowContext(ctx, `SELECT owner_id FROM servers WHERE id = $1`, serverID).Scan(&ownerID)
	if err == nil && ownerID == userID {
		return true, nil
	}

	perms, err := c.GetUserPermissions(ctx, serverID, userID)
	if err != nil {
		return false, err
	}

	// Administrator bypasses everything.
	if Has(perms, ADMINISTRATOR) {
		return true, nil
	}

	return Has(perms, required), nil
}

// CheckChannelPermission reports whether the user has the required permission bits
// for a specific channel. It layers channel-level overrides on top of server role
// permissions. ADMINISTRATOR bypasses all checks.
func (c *Checker) CheckChannelPermission(ctx context.Context, serverID, userID, channelID string, required int64) (bool, error) {
	// Check if the user is the server owner.
	var ownerID string
	err := c.db.QueryRowContext(ctx, `SELECT owner_id FROM servers WHERE id = $1`, serverID).Scan(&ownerID)
	if err == nil && ownerID == userID {
		return true, nil
	}

	perms, err := c.GetUserPermissions(ctx, serverID, userID)
	if err != nil {
		return false, err
	}

	// Administrator bypasses everything.
	if Has(perms, ADMINISTRATOR) {
		return true, nil
	}

	// Layer channel overrides: apply deny first (subtract), then allow (add).
	// Role-based overrides from all user roles, plus the @everyone role, then user-specific override.
	rows, err := c.db.QueryContext(ctx, `
		SELECT co.allow_bitflags, co.deny_bitflags
		FROM channel_overrides co
		WHERE co.channel_id = $1 AND co.target_type = 'role'
		AND (
			co.target_id IN (SELECT mr.role_id FROM member_roles mr WHERE mr.user_id = $2 AND mr.server_id = $3)
			OR co.target_id = (SELECT id FROM roles WHERE server_id = $3 AND is_default = TRUE LIMIT 1)
		)
	`, channelID, userID, serverID)
	if err != nil {
		return false, err
	}

	var allow, deny int64
	if rows != nil {
		for rows.Next() {
			var a, d int64
			if err := rows.Scan(&a, &d); err != nil {
				rows.Close()
				return false, err
			}
			allow |= a
			deny |= d
		}
		rows.Close()
	}

	// User-specific override.
	var ua, ud int64
	err = c.db.QueryRowContext(ctx, `
		SELECT allow_bitflags, deny_bitflags FROM channel_overrides
		WHERE channel_id = $1 AND target_type = 'user' AND target_id = $2
	`, channelID, userID).Scan(&ua, &ud)
	if err != nil && err != sql.ErrNoRows {
		return false, err
	}
	allow |= ua
	deny |= ud

	// Apply deny first, then allow.
	perms = (perms &^ deny) | allow

	return Has(perms, required), nil
}
