package taskboard

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

func (txs TxStore) taskDiscussionAuthorizationAudit(ctx context.Context, queryer coredb.QueryExecer, workspaceID string, taskID string, actorMemberID string, allowAgentAuthor bool) (string, string, string, error) {
	anchor, err := txs.loadTaskCollaboratorTaskAnchor(ctx, queryer, workspaceID, taskID)
	if err != nil {
		return "", "", "", err
	}
	if strings.TrimSpace(actorMemberID) == strings.TrimSpace(anchor.ExecutionOwnerMemberID) {
		return anchor.ExecutionOwnerMemberID, taskDiscussionAuthorizationOwner, "[]", nil
	}
	if allowAgentAuthor {
		if err := ensureActiveTaskDiscussionAgentActor(ctx, queryer, workspaceID, actorMemberID); err != nil {
			return "", "", "", err
		}
		return anchor.ExecutionOwnerMemberID, taskDiscussionAuthorizationAgent, "[]", nil
	}

	sources := taskEffectiveCollaboratorTargets(anchor)
	now := time.Now().UTC()
	// Authorization is a read-only boundary. Expired grants are excluded by
	// the query predicate; maintenance cleanup remains on collaborator
	// mutation/list paths and must not be a side effect of an authority probe.
	rows, err := txs.loadActiveTaskCollaboratorsForTargets(ctx, queryer, sources, now, false)
	if err != nil {
		return "", "", "", err
	}
	relationSources := make([]TaskCollaboratorSource, 0)
	for _, row := range rows {
		if row.MemberID != actorMemberID {
			continue
		}
		relationSources = append(relationSources, TaskCollaboratorSource{
			ScopeType:  row.ScopeType,
			ScopeID:    row.ScopeID,
			RelationID: row.ID,
			Role:       row.Role,
		})
	}
	if len(relationSources) == 0 {
		return "", "", "", ErrForbidden
	}
	sort.Slice(relationSources, func(i, j int) bool {
		if relationSources[i].ScopeType == relationSources[j].ScopeType {
			return relationSources[i].RelationID < relationSources[j].RelationID
		}
		return collaboratorScopeRank(relationSources[i].ScopeType) < collaboratorScopeRank(relationSources[j].ScopeType)
	})
	payload, err := model.MarshalTaskCollaboratorSources(taskCollaboratorSourcePayloads(relationSources))
	if err != nil {
		return "", "", "", fmt.Errorf("marshal task discussion collaborator audit: %w", err)
	}
	return anchor.ExecutionOwnerMemberID, taskDiscussionAuthorizationCollaborator, string(payload), nil
}

func taskCollaboratorSourcePayloads(sources []TaskCollaboratorSource) []model.TaskCollaboratorSourceJSON {
	if len(sources) == 0 {
		return nil
	}
	payloads := make([]model.TaskCollaboratorSourceJSON, 0, len(sources))
	for _, source := range sources {
		payloads = append(payloads, model.TaskCollaboratorSourceJSON{
			ScopeType:  source.ScopeType,
			ScopeID:    source.ScopeID,
			RelationID: source.RelationID,
			Role:       source.Role,
		})
	}
	return payloads
}

func ensureActiveTaskDiscussionAgentActor(ctx context.Context, queryer coredb.QueryExecer, workspaceID string, actorMemberID string) error {
	row := queryer.QueryRow(ctx, `
SELECT 1
FROM members
WHERE workspace_id = $1
  AND id = $2
  AND type = 'agent'
  AND status = 'active'
`, workspaceID, actorMemberID)
	var marker int
	if err := row.Scan(&marker); err != nil {
		if coredb.IsNotFound(err) {
			return ErrForbidden
		}
		return fmt.Errorf("load task discussion agent author: %w", err)
	}
	return nil
}

func (txs TxStore) ensureTaskScopeExecution(ctx context.Context, queryer coredb.QueryExecer, target taskCollaboratorTarget, now time.Time) (taskScopeExecution, error) {
	execution, err := txs.loadTaskScopeExecution(ctx, queryer, target)
	if err == nil {
		return execution, nil
	}
	if !errors.Is(err, ErrNotFound) {
		return taskScopeExecution{}, err
	}

	executionID, err := newTaskScopeExecutionID()
	if err != nil {
		return taskScopeExecution{}, err
	}
	if _, err := queryer.Exec(ctx, `
INSERT INTO task_scope_executions (
    id, workspace_id, owner_member_id, scope_type, scope_id, created_at, updated_at
) VALUES ($1, $2, $3, $4, $5, $6, $6)
`, executionID, target.WorkspaceID, target.ExecutionOwnerMemberID, target.ScopeType, target.ScopeID, now); err != nil {
		return taskScopeExecution{}, fmt.Errorf("insert task scope execution: %w", err)
	}
	return taskScopeExecution{
		ID:            executionID,
		WorkspaceID:   target.WorkspaceID,
		OwnerMemberID: target.ExecutionOwnerMemberID,
		ScopeType:     target.ScopeType,
		ScopeID:       target.ScopeID,
	}, nil
}

func (txs TxStore) loadTaskScopeExecution(ctx context.Context, queryer coredb.QueryExecer, target taskCollaboratorTarget) (taskScopeExecution, error) {
	row := queryer.QueryRow(ctx, `
SELECT id, workspace_id, owner_member_id, scope_type, scope_id
FROM task_scope_executions
WHERE workspace_id = $1
  AND owner_member_id = $2
  AND scope_type = $3
  AND scope_id = $4
`, target.WorkspaceID, target.ExecutionOwnerMemberID, target.ScopeType, target.ScopeID)
	var execution taskScopeExecution
	if err := row.Scan(&execution.ID, &execution.WorkspaceID, &execution.OwnerMemberID, &execution.ScopeType, &execution.ScopeID); err != nil {
		if coredb.IsNotFound(err) {
			return taskScopeExecution{}, ErrNotFound
		}
		return taskScopeExecution{}, fmt.Errorf("load task scope execution: %w", err)
	}
	return execution, nil
}

func cleanupExpiredTaskScopeParticipants(ctx context.Context, queryer coredb.QueryExecer, executions []taskScopeExecution, now time.Time) error {
	if len(executions) == 0 {
		return nil
	}
	args := make([]any, 0, len(executions)+1)
	clauses := make([]string, 0, len(executions))
	for _, execution := range executions {
		if strings.TrimSpace(execution.ID) == "" {
			continue
		}
		clauses = append(clauses, fmt.Sprintf("owner_scope_execution_id = $%d", len(args)+1))
		args = append(args, execution.ID)
	}
	if len(clauses) == 0 {
		return nil
	}
	nowArg := len(args) + 1
	args = append(args, now)
	query := fmt.Sprintf(`
UPDATE task_scope_participant_metadata
SET removed_at = $%d,
    active_key = id,
    updated_at = $%d
WHERE (`+strings.Join(clauses, " OR ")+`)
  AND removed_at IS NULL
  AND left_at IS NULL
  AND expires_at IS NOT NULL
  AND expires_at <= $%d
`, nowArg, nowArg, nowArg)
	if _, err := queryer.Exec(ctx, query, args...); err != nil {
		return fmt.Errorf("expire task scope participant metadata: %w", err)
	}
	return nil
}

func taskScopeParticipantActiveKey(executionID string, memberID string) string {
	return taskScopeActiveKey("people", executionID, memberID)
}

func taskScopeActiveKey(kind string, parts ...string) string {
	hash := sha256.New()
	hash.Write([]byte(strings.TrimSpace(kind)))
	for _, part := range parts {
		hash.Write([]byte{0})
		hash.Write([]byte(strings.TrimSpace(part)))
	}
	return "tskact_" + hex.EncodeToString(hash.Sum(nil))
}
