package orchestrator

import (
	"context"
	"errors"
	"fmt"
	"strings"

	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/invocation"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/plan"
	"github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
	invocationlang "github.com/tidbcloud/AgentTeam/internal/workflow/invocation"
)

var errPermanentMaterial = errors.New("workflow Run material is permanently unavailable")

// IsPermanentMaterialError reports whether a loader failure is proven to be
// deterministic. Unknown/database errors must remain retryable: a transient
// read outage is never a workflow business failure.
func IsPermanentMaterialError(err error) bool {
	if err == nil {
		return false
	}
	return errors.Is(err, errPermanentMaterial)
}

func permanentMaterialError(message string, cause error) error {
	if cause == nil {
		return fmt.Errorf("%w: %s", errPermanentMaterial, message)
	}
	return fmt.Errorf("%w: %s: %v", errPermanentMaterial, message, cause)
}

type PinnedPlanVersionStore interface {
	LoadVersionByID(context.Context, string, string, string) (plan.PlanVersion, error)
}

type PinnedInvocationStore interface {
	LoadInvocation(context.Context, string, string) (invocation.Invocation, error)
}

// PinnedMaterialLoader joins only the two immutable authorities captured by
// Invocation acceptance: the exact PlanVersion artifact and the exact
// Invocation source provenance. It deliberately never reads a Plan
// current/default pointer or a Binding active revision.
type PinnedMaterialLoader struct {
	plans       PinnedPlanVersionStore
	invocations PinnedInvocationStore
}

func NewPinnedMaterialLoader(plans PinnedPlanVersionStore, invocations PinnedInvocationStore) (*PinnedMaterialLoader, error) {
	if plans == nil || invocations == nil {
		return nil, fmt.Errorf("workflow Run PlanVersion and Invocation stores are required")
	}
	return &PinnedMaterialLoader{plans: plans, invocations: invocations}, nil
}

func (loader *PinnedMaterialLoader) LoadRunMaterial(ctx context.Context, run run.Run) (Material, error) {
	if loader == nil || loader.plans == nil || loader.invocations == nil {
		return Material{}, fmt.Errorf("workflow Run material loader is not configured")
	}
	version, err := loader.plans.LoadVersionByID(ctx, run.WorkspaceID, run.PlanID, run.PlanVersionID)
	if err != nil {
		if errors.Is(err, storeerr.ErrNotFound) {
			return Material{}, permanentMaterialError("exact workflow PlanVersion does not exist", err)
		}
		return Material{}, fmt.Errorf("load exact workflow PlanVersion: %w", err)
	}
	invocation, err := loader.invocations.LoadInvocation(ctx, run.WorkspaceID, run.InvocationID)
	if err != nil {
		if errors.Is(err, storeerr.ErrNotFound) {
			return Material{}, permanentMaterialError("exact workflow Invocation does not exist", err)
		}
		return Material{}, fmt.Errorf("load exact workflow Invocation: %w", err)
	}
	if invocation.WorkspaceID != run.WorkspaceID || invocation.ID != run.InvocationID || invocation.RunID != run.ID ||
		invocation.PlanID != run.PlanID || invocation.PlanVersionID != run.PlanVersionID ||
		invocation.InputDigest != run.InputDigest || invocation.Identity != run.Identity ||
		strings.TrimSpace(invocation.WorkflowNameSnapshot) != strings.TrimSpace(run.WorkflowNameSnapshot) {
		return Material{}, permanentMaterialError("immutable Invocation snapshot does not match", nil)
	}
	if version.WorkspaceID != run.WorkspaceID || version.PlanID != run.PlanID || version.ID != run.PlanVersionID ||
		strings.TrimSpace(version.CompilerVersion) == "" || strings.TrimSpace(version.Digest) == "" || len(version.Artifact) == 0 {
		return Material{}, permanentMaterialError("immutable PlanVersion snapshot does not match", nil)
	}
	sourceKind := strings.TrimSpace(invocation.SourceKind)
	if sourceKind != invocationlang.SourceKindManual && sourceKind != invocationlang.SourceKindSchedule {
		return Material{}, permanentMaterialError(fmt.Sprintf("Invocation source kind %q is invalid", sourceKind), nil)
	}
	return Material{
		WorkspaceID: run.WorkspaceID, PlanID: run.PlanID, PlanVersionID: run.PlanVersionID,
		CompilerVersion: strings.TrimSpace(version.CompilerVersion), Digest: strings.TrimSpace(version.Digest),
		ArtifactJSON: append([]byte(nil), version.Artifact...), SourceKind: sourceKind,
	}, nil
}

var _ MaterialLoader = (*PinnedMaterialLoader)(nil)
