package review

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// projectUserForAgent resolves the user-level Project actor from the trusted
// machine on the authenticated Agent principal. Projects belong to users, not
// individual Agent members: all Agents backed by the same user's machine share
// the same human Project visibility and ownership semantics.
func (store *Store) projectUserForAgent(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent) (string, error) {
	workspaceID := strings.TrimSpace(authenticatedAgent.WorkspaceID)
	machineID := strings.TrimSpace(authenticatedAgent.MachineID)
	if workspaceID == "" || machineID == "" {
		return "", storeerr.BadRequest("workspace and machine are required")
	}
	userID, err := access.MachineCreatedByUser(ctx, store.db, workspaceID, machineID)
	if err != nil {
		return "", err
	}
	// Fail closed if the machine owner is no longer an active human member of
	// the workspace. The human Project APIs apply the same membership check.
	if _, err := store.loadHumanWorkspaceMember(ctx, store.db, userID, workspaceID); err != nil {
		return "", err
	}
	return userID, nil
}

// ListProjectPageForAgent delegates to the human/user Project visibility model
// after deriving the user from the principal's trusted machine. The options
// use the same updatedAt DESC, id ASC keyset ordering as the human Project API.
func (store *Store) ListProjectPageForAgent(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent, options agent.ProjectListOptions) (agent.ProjectPage, error) {
	userID, err := store.projectUserForAgent(ctx, authenticatedAgent)
	if err != nil {
		return agent.ProjectPage{}, err
	}
	return store.ListProjectPageForUser(ctx, userID, authenticatedAgent.WorkspaceID, options)
}

// ListProjectsForAgent is retained for non-paginated store callers. Agent
// Runtime HTTP reads use ListProjectPageForAgent; this compatibility method
// preserves the historical complete slice semantics for internal callers.
func (store *Store) ListProjectsForAgent(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent) ([]agent.Project, error) {
	userID, err := store.projectUserForAgent(ctx, authenticatedAgent)
	if err != nil {
		return nil, err
	}
	return store.ListProjectsForUser(ctx, userID, authenticatedAgent.WorkspaceID)
}

// CreateProjectForAgent creates or reuses a user-owned Project target. The
// workspace and machine come exclusively from the authenticated principal;
// model/client values cannot override them. Reuse is allowed only when the
// existing target is owned by the same user-level workspace member.
func (store *Store) CreateProjectForAgent(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent, params agent.CreateProjectParams) (agent.Project, error) {
	userID, err := store.projectUserForAgent(ctx, authenticatedAgent)
	if err != nil {
		return agent.Project{}, err
	}
	workspaceID := strings.TrimSpace(authenticatedAgent.WorkspaceID)
	machineID := strings.TrimSpace(authenticatedAgent.MachineID)
	normalized, err := agent.ValidateCreateProjectParams(agent.CreateProjectParams{
		WorkspaceID: workspaceID,
		MachineID:   machineID,
		RootPath:    params.RootPath,
		Name:        params.Name,
		Description: params.Description,
		Visibility:  params.Visibility,
	})
	if err != nil {
		return agent.Project{}, err
	}
	targetHash := projectTargetHash(workspaceID, machineID, normalized.RootPath)
	id, err := identifiers.NewEntityID(projectIDPrefix)
	if err != nil {
		return agent.Project{}, fmt.Errorf("generate project id: %w", err)
	}
	now := time.Now().UTC()
	row := projectRow{
		ID:              id,
		WorkspaceID:     workspaceID,
		MachineID:       machineID,
		RootPath:        normalized.RootPath,
		TargetHash:      targetHash,
		Name:            normalized.Name,
		Description:     normalized.Description,
		Visibility:      normalized.Visibility,
		OwnerUserID:     userID,
		CreatedByUserID: userID,
		UpdatedByUserID: userID,
		CreatedAt:       now,
		UpdatedAt:       now,
	}
	if _, err := store.db.Exec(ctx, `
INSERT INTO projects (`+projectColumns+`)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
`, row.ID, row.WorkspaceID, row.MachineID, row.RootPath, row.TargetHash,
		row.Name, row.Description, row.Visibility, row.OwnerUserID,
		row.CreatedByUserID, row.UpdatedByUserID, row.CreatedAt, row.UpdatedAt); err != nil {
		if !coredb.IsUniqueViolation(err) {
			return agent.Project{}, fmt.Errorf("create project for agent: %w", err)
		}
		existing, loadErr := store.getProjectByTargetHash(ctx, workspaceID, targetHash)
		if loadErr != nil {
			return agent.Project{}, loadErr
		}
		if existing.OwnerUserID != userID {
			return agent.Project{}, storeerr.Conflict("project target already exists for another user")
		}
		return existing, nil
	}
	return row.toModel(), nil
}

// DeleteProjectForAgent applies the human/user owner check after deriving the
// user from the principal's trusted machine.
func (store *Store) DeleteProjectForAgent(ctx context.Context, authenticatedAgent agent.AuthenticatedAgent, projectID string) error {
	userID, err := store.projectUserForAgent(ctx, authenticatedAgent)
	if err != nil {
		return err
	}
	return store.DeleteProjectForUser(ctx, userID, authenticatedAgent.WorkspaceID, projectID)
}
