package channel

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

// ChannelPermission records that a workspace member holds a channel-level
// permission grant (today: channel-manager rights) without being the channel
// owner.
type ChannelPermission struct {
	ID          string
	WorkspaceID string
	ChannelID   string
	MemberID    string
	UserID      string
	DisplayName string
	CreatedBy   string
	CreatedAt   time.Time
}

// ErrChannelPermissionAlreadyGranted indicates the member already holds the
// permission for the (workspace, channel) pair.
var ErrChannelPermissionAlreadyGranted = errors.New("member already holds the channel permission")

// ErrChannelPermissionNotGranted indicates the member does not hold the
// permission for the (workspace, channel) pair.
var ErrChannelPermissionNotGranted = errors.New("member does not hold the channel permission")

const channelPermissionIDPrefix = "chperm"

// ListChannelPermissions returns every permission grant on the channel,
// ordered by grant time. The caller is responsible for workspace/channel
// access checks before calling this method.
func (store *Store) ListChannelPermissions(ctx context.Context, workspaceID string, channelID string) ([]ChannelPermission, error) {
	if store == nil {
		return nil, fmt.Errorf("channel store is not configured")
	}
	if t := trim(workspaceID, channelID); t.hasEmpty {
		return nil, badRequest("workspace_id and channel_id are required")
	}
	rows, err := store.db.Query(ctx, `
SELECT id, workspace_id, channel_id, member_id, user_id, display_name, created_by, created_at
FROM channel_permissions
WHERE workspace_id = $1 AND channel_id = $2
ORDER BY created_at ASC
`, workspaceID, channelID)
	if err != nil {
		return nil, fmt.Errorf("list channel permissions: %w", err)
	}
	defer rows.Close()
	permissions := make([]ChannelPermission, 0)
	for rows.Next() {
		var p ChannelPermission
		if err := rows.Scan(&p.ID, &p.WorkspaceID, &p.ChannelID, &p.MemberID, &p.UserID, &p.DisplayName, &p.CreatedBy, &p.CreatedAt); err != nil {
			return nil, fmt.Errorf("scan channel permission: %w", err)
		}
		permissions = append(permissions, p)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate channel permissions: %w", err)
	}
	return permissions, nil
}

// InsertChannelPermission grants the channel permission to a member.
// Returns ErrChannelPermissionAlreadyGranted on duplicate (workspace, channel,
// member).
func (store *Store) InsertChannelPermission(ctx context.Context, workspaceID string, channelID string, memberID string, userID string, displayName string, createdBy string) (ChannelPermission, error) {
	if store == nil {
		return ChannelPermission{}, fmt.Errorf("channel store is not configured")
	}
	if t := trim(workspaceID, channelID, memberID, userID, createdBy); t.hasEmpty {
		return ChannelPermission{}, badRequest("workspace_id, channel_id, member_id, user_id and created_by are required")
	}
	id, err := identifiers.NewEntityID(channelPermissionIDPrefix)
	if err != nil {
		return ChannelPermission{}, fmt.Errorf("generate channel permission id: %w", err)
	}
	now := time.Now().UTC()
	permission := ChannelPermission{
		ID:          id,
		WorkspaceID: workspaceID,
		ChannelID:   channelID,
		MemberID:    memberID,
		UserID:      userID,
		DisplayName: displayName,
		CreatedBy:   createdBy,
		CreatedAt:   now,
	}
	_, err = store.db.Exec(ctx, `
INSERT INTO channel_permissions (id, workspace_id, channel_id, member_id, user_id, display_name, created_by, created_at)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
`, permission.ID, permission.WorkspaceID, permission.ChannelID, permission.MemberID, permission.UserID, permission.DisplayName, permission.CreatedBy, permission.CreatedAt)
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			return ChannelPermission{}, ErrChannelPermissionAlreadyGranted
		}
		return ChannelPermission{}, fmt.Errorf("insert channel permission: %w", err)
	}
	return permission, nil
}

// DeleteChannelPermission revokes the channel permission for a member.
// Returns ErrChannelPermissionNotGranted if no grant exists.
func (store *Store) DeleteChannelPermission(ctx context.Context, workspaceID string, channelID string, memberID string) error {
	if store == nil {
		return fmt.Errorf("channel store is not configured")
	}
	if t := trim(workspaceID, channelID, memberID); t.hasEmpty {
		return badRequest("workspace_id, channel_id and member_id are required")
	}
	result, err := store.db.Exec(ctx, `
DELETE FROM channel_permissions
WHERE workspace_id = $1 AND channel_id = $2 AND member_id = $3
`, workspaceID, channelID, memberID)
	if err != nil {
		return fmt.Errorf("delete channel permission: %w", err)
	}
	if result.RowsAffected() == 0 {
		return ErrChannelPermissionNotGranted
	}
	return nil
}

// IsChannelPermissionHolderForUser reports whether the workspace member
// resolved from (userID, workspaceID) currently holds the channel permission
// on channelID. Returns false (no error) if the member has no grant.
func (store *Store) IsChannelPermissionHolderForUser(ctx context.Context, userID string, workspaceID string, channelID string) (bool, error) {
	if store == nil {
		return false, fmt.Errorf("channel store is not configured")
	}
	memberID, err := store.requireWorkspaceAccess(ctx, userID, workspaceID)
	if err != nil {
		return false, err
	}
	var exists bool
	err = store.db.QueryRow(ctx, `
SELECT EXISTS(
  SELECT 1 FROM channel_permissions
  WHERE workspace_id = $1 AND channel_id = $2 AND member_id = $3
)
`, workspaceID, channelID, memberID).Scan(&exists)
	if err != nil {
		return false, fmt.Errorf("check channel permission holder: %w", err)
	}
	return exists, nil
}

type trimmedFields struct {
	hasEmpty bool
}

func trim(values ...string) trimmedFields {
	out := trimmedFields{}
	for _, v := range values {
		if isBlank(v) {
			out.hasEmpty = true
			break
		}
	}
	return out
}

func isBlank(s string) bool {
	for i := 0; i < len(s); i++ {
		if s[i] != ' ' && s[i] != '\t' && s[i] != '\n' && s[i] != '\r' {
			return false
		}
	}
	return true
}
