package devsearch

import (
	"context"
	"errors"
	"fmt"
	"io"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

const defaultEnsureBackfillBatchSize = 1000

type WorkspaceSearchReadyResult struct {
	WorkspaceID             string `json:"workspace_id"`
	BackfillStatus          string `json:"backfill_status"`
	BackfillCursorGlobalSeq int64  `json:"backfill_cursor_global_seq"`
	FinalizeStatus          string `json:"finalize_status"`
	ReadReady               bool   `json:"read_ready"`
	FastPath                bool   `json:"fast_path,omitempty"`
}

type EnsureAllWorkspacesResult struct {
	Ready          bool                         `json:"ready"`
	FastPath       bool                         `json:"fast_path"`
	WorkspaceCount int                          `json:"workspace_count"`
	ProcessedCount int                          `json:"processed_count"`
	CurrentPhase   string                       `json:"current_phase,omitempty"`
	Error          string                       `json:"error,omitempty"`
	Workspaces     []WorkspaceSearchReadyResult `json:"workspaces"`
}

type SearchReadinessStore interface {
	ListWorkspaceIDs(ctx context.Context) ([]string, error)
	LoadWorkspaceGlobalReadiness(ctx context.Context, workspaceID string) (message.ScopeSearchReadiness, error)
	BackfillWorkspaceGlobal(ctx context.Context, workspaceID string, batchSize int, updatedAt time.Time) (message.ScopeSearchBackfillResult, error)
	FinalizeWorkspaceGlobal(ctx context.Context, workspaceID string, updatedAt time.Time) (message.ScopeSearchReadiness, error)
	PromoteWorkspaceGlobal(ctx context.Context, workspaceID string, updatedAt time.Time) (message.ScopeSearchReadiness, error)
}

type SQLSearchReadinessStore struct {
	queryer coredb.QueryExecer
	repos   message.ScopeRepositories
}

func NewSQLSearchReadinessStore(queryer coredb.QueryExecer) SQLSearchReadinessStore {
	return SQLSearchReadinessStore{
		queryer: queryer,
		repos:   message.NewScopeRepositories(queryer),
	}
}

func (store SQLSearchReadinessStore) ListWorkspaceIDs(ctx context.Context) ([]string, error) {
	if store.queryer == nil {
		return nil, fmt.Errorf("search readiness queryer is nil")
	}
	return workspace.ListWorkspaceIDs(ctx, store.queryer)
}

func (store SQLSearchReadinessStore) LoadWorkspaceGlobalReadiness(ctx context.Context, workspaceID string) (message.ScopeSearchReadiness, error) {
	return store.repos.Search.LoadReadiness(ctx, strings.TrimSpace(workspaceID), "", "")
}

func (store SQLSearchReadinessStore) BackfillWorkspaceGlobal(ctx context.Context, workspaceID string, batchSize int, updatedAt time.Time) (message.ScopeSearchBackfillResult, error) {
	return store.repos.Search.Backfill(ctx, strings.TrimSpace(workspaceID), "", "", batchSize, updatedAt)
}

func (store SQLSearchReadinessStore) FinalizeWorkspaceGlobal(ctx context.Context, workspaceID string, updatedAt time.Time) (message.ScopeSearchReadiness, error) {
	return store.repos.Search.Finalize(ctx, strings.TrimSpace(workspaceID), "", "", updatedAt)
}

func (store SQLSearchReadinessStore) PromoteWorkspaceGlobal(ctx context.Context, workspaceID string, updatedAt time.Time) (message.ScopeSearchReadiness, error) {
	return store.repos.Search.PromoteReady(ctx, strings.TrimSpace(workspaceID), "", "", updatedAt)
}

func EnsureAllWorkspacesSearchReady(ctx context.Context, store SearchReadinessStore, batchSize int, logw io.Writer) (EnsureAllWorkspacesResult, error) {
	if ctx == nil {
		ctx = context.Background()
	}
	if logw == nil {
		logw = io.Discard
	}
	if store == nil {
		return EnsureAllWorkspacesResult{}, fmt.Errorf("search readiness store is required")
	}
	if batchSize <= 0 {
		batchSize = defaultEnsureBackfillBatchSize
	}
	workspaceIDs, err := store.ListWorkspaceIDs(ctx)
	if err != nil {
		return EnsureAllWorkspacesResult{}, err
	}
	result := EnsureAllWorkspacesResult{
		Ready:          true,
		FastPath:       true,
		WorkspaceCount: len(workspaceIDs),
		Workspaces:     make([]WorkspaceSearchReadyResult, 0, len(workspaceIDs)),
	}
	for _, workspaceID := range workspaceIDs {
		workspaceResult, err := EnsureWorkspaceGlobalSearchReady(ctx, store, workspaceID, batchSize, logw)
		result.Workspaces = append(result.Workspaces, workspaceResult)
		if !workspaceResult.FastPath {
			result.FastPath = false
		}
		if err != nil {
			result.Ready = false
			result.ProcessedCount = len(result.Workspaces)
			result.CurrentPhase = phaseFromError(err)
			if result.CurrentPhase == "" {
				result.CurrentPhase = detectFailedPhase(workspaceResult)
			}
			result.Error = err.Error()
			return result, err
		}
	}
	result.ProcessedCount = len(result.Workspaces)
	return result, nil
}

func EnsureWorkspaceGlobalSearchReady(ctx context.Context, store SearchReadinessStore, workspaceID string, batchSize int, logw io.Writer) (WorkspaceSearchReadyResult, error) {
	if ctx == nil {
		ctx = context.Background()
	}
	if logw == nil {
		logw = io.Discard
	}
	workspaceID = strings.TrimSpace(workspaceID)
	if workspaceID == "" {
		return WorkspaceSearchReadyResult{}, fmt.Errorf("workspace id is required")
	}
	if batchSize <= 0 {
		batchSize = defaultEnsureBackfillBatchSize
	}
	state, err := store.LoadWorkspaceGlobalReadiness(ctx, workspaceID)
	if err == nil && state.Ready() {
		fmt.Fprintf(logw, "[workspace=%s] already ready\n", workspaceID)
		return readinessResultFromState(state, true), nil
	}
	if err != nil && !errors.Is(err, message.ErrNotFound) {
		return WorkspaceSearchReadyResult{WorkspaceID: workspaceID}, wrapWorkspacePhaseError("load", fmt.Errorf("load workspace search readiness for %s: %w", workspaceID, err))
	}

	fmt.Fprintf(logw, "[workspace=%s] backfill workspace-global projection\n", workspaceID)
	for {
		backfill, backfillErr := store.BackfillWorkspaceGlobal(ctx, workspaceID, batchSize, time.Now().UTC())
		if backfillErr != nil {
			return readinessResultFromState(backfill.State, false), wrapWorkspacePhaseError("backfill", fmt.Errorf("backfill workspace-global search for %s: %w", workspaceID, backfillErr))
		}
		state = backfill.State
		if state.BackfillStatus == "complete" {
			break
		}
	}

	fmt.Fprintf(logw, "[workspace=%s] finalize workspace-global projection\n", workspaceID)
	state, err = store.FinalizeWorkspaceGlobal(ctx, workspaceID, time.Now().UTC())
	if err != nil {
		return readinessResultFromState(state, false), wrapWorkspacePhaseError("finalize", fmt.Errorf("finalize workspace-global search for %s: %w", workspaceID, err))
	}

	fmt.Fprintf(logw, "[workspace=%s] promote workspace-global projection read-ready\n", workspaceID)
	state, err = store.PromoteWorkspaceGlobal(ctx, workspaceID, time.Now().UTC())
	if err != nil {
		return readinessResultFromState(state, false), wrapWorkspacePhaseError("promote-ready", fmt.Errorf("promote workspace-global search for %s: %w", workspaceID, err))
	}
	return readinessResultFromState(state, false), nil
}

func readinessResultFromState(state message.ScopeSearchReadiness, fastPath bool) WorkspaceSearchReadyResult {
	return WorkspaceSearchReadyResult{
		WorkspaceID:             strings.TrimSpace(state.WorkspaceID),
		BackfillStatus:          strings.TrimSpace(state.BackfillStatus),
		BackfillCursorGlobalSeq: state.BackfillCursorGlobalSeq,
		FinalizeStatus:          strings.TrimSpace(state.FinalizeStatus),
		ReadReady:               state.ReadReady,
		FastPath:                fastPath,
	}
}

func detectFailedPhase(result WorkspaceSearchReadyResult) string {
	if !result.ReadReady && result.FinalizeStatus != "complete" {
		return "finalize"
	}
	if result.BackfillStatus != "complete" {
		return "backfill"
	}
	if !result.ReadReady {
		return "promote-ready"
	}
	return "complete"
}

type workspacePhaseError struct {
	phase string
	err   error
}

func (err workspacePhaseError) Error() string {
	return err.err.Error()
}

func (err workspacePhaseError) Unwrap() error {
	return err.err
}

func wrapWorkspacePhaseError(phase string, err error) error {
	if err == nil {
		return nil
	}
	return workspacePhaseError{phase: strings.TrimSpace(phase), err: err}
}

func phaseFromError(err error) string {
	var phaseErr workspacePhaseError
	if errors.As(err, &phaseErr) {
		return strings.TrimSpace(phaseErr.phase)
	}
	return ""
}
