package flow

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"sort"
	"text/template"
	"time"

	"github.com/google/uuid"
	"go-micro.dev/v6/ai"
	"go-micro.dev/v6/client"
	codecbytes "go-micro.dev/v6/codec/bytes"
	"go-micro.dev/v6/gateway/a2a"
	"go-micro.dev/v6/logger"
	"go-micro.dev/v6/store"
)

// State carries data across the steps of a flow run. It is a struct, not
// a map: Data is the serialized payload (set and read with Set/Scan), and
// Stage names the step the run is at — so you can always tell where it is,
// and the engine uses it as the resume point.
type State struct {
	Stage string `json:"stage"`
	Data  []byte `json:"data"`
}

// Set replaces the data with the JSON encoding of v.
func (s *State) Set(v any) error {
	b, err := json.Marshal(v)
	if err != nil {
		return err
	}
	s.Data = b
	return nil
}

// Scan decodes the data into v (a pointer to the caller's struct).
func (s State) Scan(v any) error {
	if len(s.Data) == 0 {
		return nil
	}
	return json.Unmarshal(s.Data, v)
}

// String returns the data as a string, for text payloads.
func (s State) String() string { return string(s.Data) }

// StepFunc performs one step's work: it receives the carried state and
// returns the next state.
type StepFunc func(ctx context.Context, in State) (State, error)

// Verifier grades a step output before the flow advances. Returning
// Passed=false converts the grade into a retryable VerificationError, so
// the existing step retry/supervision path can feed Feedback into the next
// attempt through ai.RunInfo.VerificationFeedback.
type Verifier func(ctx context.Context, out State) (Verification, error)

// Verification is the verifier's deterministic grade for one step attempt.
type Verification struct {
	Passed   bool
	Feedback string
}

// VerificationError reports a failed grade. It is returned from runStep so
// existing retry, checkpoint, and trace paths handle verifier failures the
// same way they handle step execution failures.
type VerificationError struct {
	Step     string
	Feedback string
}

func (e *VerificationError) Error() string {
	if e.Feedback == "" {
		return fmt.Sprintf("flow: verification failed for step %q", e.Step)
	}
	return fmt.Sprintf("flow: verification failed for step %q: %s", e.Step, e.Feedback)
}

// Step is one unit of a flow — a named action with optional retry and
// verification hooks. There is one Step kind; the action is the Run func,
// and the Call/LLM/Agent helpers produce the common ones.
type Step struct {
	Name   string
	Run    StepFunc
	Retry  int      // per-step override of the flow's retry (0 = use the flow default)
	Verify Verifier // optional grade; failed grades retry the step with feedback in RunInfo
}

// StepRecord is the recorded outcome of one step within a run.
type StepRecord struct {
	Name               string `json:"name"`
	Status             string `json:"status"` // pending | in_progress | done | failed
	Attempts           int    `json:"attempts"`
	Result             string `json:"result,omitempty"`
	Error              string `json:"error,omitempty"`
	ErrorKind          string `json:"error_kind,omitempty"`
	VerificationStatus string `json:"verification_status,omitempty"` // passed | failed
	VerificationNote   string `json:"verification_note,omitempty"`
}

// Run is the persisted record of one flow execution — what a Checkpoint
// saves and loads. It is retained for success and failure unless the flow
// opts into cleanup with DeleteOnSuccess.
type Run struct {
	ID       string       `json:"id"`
	ParentID string       `json:"parent_id,omitempty"`
	Flow     string       `json:"flow"`
	State    State        `json:"state"`
	Steps    []StepRecord `json:"steps"`
	Status   string       `json:"status"` // running | done | failed
	Started  time.Time    `json:"started"`
	Updated  time.Time    `json:"updated"`
}

// Checkpoint persists and restores flow runs so a run survives a crash
// and resumes where it stopped. The built-in StoreCheckpoint is
// store-backed; implement this interface to plug in another durable
// execution backend.
type Checkpoint interface {
	Save(ctx context.Context, run Run) error
	Load(ctx context.Context, runID string) (Run, bool, error)
	Delete(ctx context.Context, runID string) error
	List(ctx context.Context) ([]Run, error)
}

type storeCheckpoint struct {
	store store.Store
}

// StoreCheckpoint returns a store-backed Checkpoint that keeps a flow's
// runs in their own store table — pass the flow name as scope, so one
// flow's runs never share a table with another's (or with service or
// agent state). A nil store uses store.DefaultStore.
func StoreCheckpoint(s store.Store, scope string) Checkpoint {
	if s == nil {
		s = store.DefaultStore
	}
	// Confine runs to the "flow" database, one table per flow name. The
	// scoped handle injects this per-operation without mutating s.
	return &storeCheckpoint{store: store.Scope(s, "flow", scope)}
}

func (c *storeCheckpoint) Save(ctx context.Context, run Run) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	run.Updated = time.Now()
	b, err := json.Marshal(run)
	if err != nil {
		return err
	}
	if err := ctx.Err(); err != nil {
		return err
	}
	return c.store.Write(&store.Record{Key: run.ID, Value: b})
}

func (c *storeCheckpoint) Load(ctx context.Context, runID string) (Run, bool, error) {
	if err := ctx.Err(); err != nil {
		return Run{}, false, err
	}
	recs, err := c.store.Read(runID)
	if err == store.ErrNotFound || len(recs) == 0 {
		return Run{}, false, nil
	}
	if err != nil {
		return Run{}, false, err
	}
	var run Run
	if err := json.Unmarshal(recs[0].Value, &run); err != nil {
		return Run{}, false, err
	}
	return run, true, nil
}

func (c *storeCheckpoint) Delete(ctx context.Context, runID string) error {
	if err := ctx.Err(); err != nil {
		return err
	}
	return c.store.Delete(runID)
}

func (c *storeCheckpoint) List(ctx context.Context) ([]Run, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}
	keys, err := c.store.List()
	if err != nil {
		return nil, err
	}
	var runs []Run
	for _, id := range keys {
		if run, ok, err := c.Load(ctx, id); err == nil && ok {
			runs = append(runs, run)
		}
	}
	sort.SliceStable(runs, func(i, j int) bool {
		if runs[i].Started.Equal(runs[j].Started) {
			return runs[i].ID < runs[j].ID
		}
		return runs[i].Started.Before(runs[j].Started)
	})
	return runs, nil
}

// defaultCheckpoint returns the configured checkpoint, or a store-backed
// default scoped to the flow name when the flow has steps (durable by
// default). Scoping by name keeps each flow's runs in their own keyspace
// rather than a global one.
func defaultCheckpoint(name string, o Options) Checkpoint {
	if o.Checkpoint != nil {
		return o.Checkpoint
	}
	if len(o.Steps) > 0 {
		return StoreCheckpoint(store.DefaultStore, name)
	}
	return nil
}

// runDeps are the flow resources the Call/LLM/Agent step helpers need.
// They are injected into the context for the duration of a run so a
// StepFunc keeps the clean (ctx, State) signature.
type runDeps struct {
	client client.Client
	model  ai.Model
	tools  *ai.Tools
}

type runCtxKey struct{}

func withDeps(ctx context.Context, d *runDeps) context.Context {
	return context.WithValue(ctx, runCtxKey{}, d)
}

func depsFrom(ctx context.Context) *runDeps {
	d, _ := ctx.Value(runCtxKey{}).(*runDeps)
	return d
}

// Call returns a StepFunc that invokes an RPC endpoint, sending the
// current state Data as the request body and storing the response as the
// new Data.
func Call(service, endpoint string) StepFunc {
	return func(ctx context.Context, in State) (State, error) {
		cl := client.DefaultClient
		if d := depsFrom(ctx); d != nil && d.client != nil {
			cl = d.client
		}
		body := in.Data
		if len(body) == 0 {
			body = []byte("{}")
		}
		req := cl.NewRequest(service, endpoint, &codecbytes.Frame{Data: body})
		var rsp codecbytes.Frame
		if err := cl.Call(ctx, req, &rsp); err != nil {
			return in, err
		}
		in.Data = rsp.Data
		return in, nil
	}
}

// Dispatch returns a StepFunc that hands the current state Data (as the
// message) to a registered agent's Agent.Chat endpoint and stores the
// reply as the new Data.
func Dispatch(name string) StepFunc {
	return func(ctx context.Context, in State) (State, error) {
		cl := client.DefaultClient
		if d := depsFrom(ctx); d != nil && d.client != nil {
			cl = d.client
		}
		info, _ := ai.RunInfoFrom(ctx)
		body, _ := json.Marshal(map[string]string{"message": in.String(), "parent_id": info.RunID})
		req := cl.NewRequest(name, "Agent.Chat", &codecbytes.Frame{Data: body})
		var rsp codecbytes.Frame
		if err := cl.Call(ctx, req, &rsp); err != nil {
			return in, err
		}
		var out struct {
			Reply string `json:"reply"`
		}
		_ = json.Unmarshal(rsp.Data, &out)
		in.Data = []byte(out.Reply)
		return in, nil
	}
}

// A2A returns a StepFunc that calls a remote agent over the A2A protocol
// by URL — the cross-framework counterpart to Dispatch. It sends the
// current state Data as the message and stores the reply as the new Data.
func A2A(url string) StepFunc {
	return func(ctx context.Context, in State) (State, error) {
		reply, err := a2a.NewClient(url).Send(ctx, in.String())
		if err != nil {
			return in, err
		}
		in.Data = []byte(reply)
		return in, nil
	}
}

// LLM returns a StepFunc that runs one augmented-LLM turn: it renders the
// prompt template against the current state (.Data, .Stage), lets the
// model call the flow's services as tools, and stores the reply as the
// new Data.
func LLM(prompt string) StepFunc {
	return func(ctx context.Context, in State) (State, error) {
		d := depsFrom(ctx)
		if d == nil || d.model == nil {
			return in, fmt.Errorf("LLM step requires a flow model (set Provider/APIKey)")
		}
		text := prompt
		if tmpl, err := template.New("step").Parse(prompt); err == nil {
			var buf bytes.Buffer
			if tmpl.Execute(&buf, map[string]string{"Data": in.String(), "Stage": in.Stage}) == nil {
				text = buf.String()
			}
		}
		var tools []ai.Tool
		if d.tools != nil {
			tools, _ = d.tools.Discover()
		}
		resp, err := d.model.Generate(ctx, &ai.Request{Prompt: text, Tools: tools})
		if err != nil {
			return in, err
		}
		reply := resp.Answer
		if reply == "" {
			reply = resp.Reply
		}
		in.Data = []byte(reply)
		return in, nil
	}
}

// startRun begins a fresh run of the flow's steps with the given input.
func (f *Flow) startRun(ctx context.Context, data string) (Run, error) {
	if err := validateSteps(f.opts.Steps); err != nil {
		return Run{}, err
	}
	parentID := ""
	if info, ok := ai.RunInfoFrom(ctx); ok {
		parentID = info.RunID
	}
	run := Run{
		ID:       uuid.New().String(),
		ParentID: parentID,
		Flow:     f.name,
		State:    State{Stage: f.opts.Steps[0].Name, Data: []byte(data)},
		Status:   "running",
		Started:  time.Now(),
	}
	for _, s := range f.opts.Steps {
		run.Steps = append(run.Steps, StepRecord{Name: s.Name, Status: "pending"})
	}
	return f.runFrom(ctx, run)
}

// Resume continues a persisted run by id, picking up at the step it
// stopped on. Completed runs are a no-op.
func (f *Flow) Resume(ctx context.Context, runID string) error {
	ctx, cancel := f.withTimeout(ctx)
	defer cancel()

	if err := validateSteps(f.opts.Steps); err != nil {
		return err
	}
	if f.checkpoint == nil {
		return fmt.Errorf("flow %s has no checkpoint configured", f.name)
	}
	run, ok, err := f.checkpoint.Load(ctx, runID)
	if err != nil {
		return err
	}
	if !ok {
		return fmt.Errorf("run %s not found", runID)
	}
	if run.Status == "done" {
		return nil
	}
	_, err = f.runFrom(ctx, run)
	return err
}

// ResumePending resumes every checkpointed run for this flow that has not
// completed yet, in the same oldest-first order returned by Pending.
//
// It is a convenience for service startup and recovery loops: after a process
// restart, call ResumePending to drain the durable backlog without having to
// list and resume each run manually. If any run fails again, ResumePending
// stops and returns that run id with the error so callers can log, alert, or
// retry later without hiding the failing run.
func (f *Flow) ResumePending(ctx context.Context) (string, error) {
	ctx, cancel := f.withTimeout(ctx)
	defer cancel()

	runs, err := f.Pending(ctx)
	if err != nil {
		return "", err
	}
	for _, run := range runs {
		if err := f.Resume(ctx, run.ID); err != nil {
			return run.ID, err
		}
	}
	return "", nil
}

// Pending returns this flow's runs that have not completed — the ones a
// process restart should resume.
func (f *Flow) Pending(ctx context.Context) ([]Run, error) {
	if f.checkpoint == nil {
		return nil, nil
	}
	all, err := f.checkpoint.List(ctx)
	if err != nil {
		return nil, err
	}
	var out []Run
	for _, r := range all {
		if r.Flow == f.name && r.Status != "done" {
			out = append(out, r)
		}
	}
	return out, nil
}

// runFrom executes steps from the run's current Stage to the end,
// checkpointing before and after each step.
func (f *Flow) runFrom(ctx context.Context, run Run) (Run, error) {
	steps := f.opts.Steps
	ctx = withDeps(ctx, &runDeps{client: f.client, model: f.model, tools: f.toolSet})
	info, _ := ai.RunInfoFrom(ctx)
	info.RunID = run.ID
	info.ParentID = run.ParentID
	info.Agent = f.name
	info.Flow = f.name
	ctx = ai.WithRunInfo(ctx, info)
	ctx, finishSpan := f.startRunSpan(ctx, run)
	var spanErr error
	defer func() { finishSpan(run, spanErr) }()

	start := stepIndex(steps, run.State.Stage)
	if start < 0 {
		if run.State.Stage == "" {
			start = len(steps) // already finished
		} else {
			start = 0
		}
	}

	for i := start; i < len(steps); i++ {
		step := steps[i]
		run.State.Stage = step.Name
		run.Steps[i].Status = "in_progress"
		if err := f.save(ctx, run); err != nil {
			spanErr = err
			return run, err
		}

		out, attempts, verification, err := f.runStepSpan(ctx, step, run.State)
		run.Steps[i].Attempts = attempts
		applyVerificationRecord(&run.Steps[i], verification)
		if err != nil {
			spanErr = err
			run.Steps[i].Status = "failed"
			run.Steps[i].Error = err.Error()
			run.Steps[i].ErrorKind = string(ai.ClassifyError(err))
			run.Status = "failed"
			if saveErr := f.save(ctx, run); saveErr != nil {
				spanErr = saveErr
				return run, fmt.Errorf("%w; additionally failed to checkpoint failed run: %v", err, saveErr)
			}
			f.record(resultFromRun(f.opts.TriggerTopic, run))
			f.log.Logf(logger.ErrorLevel, "Flow %s run %s failed at step %q: %v", f.name, run.ID, step.Name, err)
			return run, err
		}

		run.State = out
		run.Steps[i].Status = "done"
		run.Steps[i].Result = truncate(out.String(), 200)
		if i+1 < len(steps) {
			run.State.Stage = steps[i+1].Name
		} else {
			run.State.Stage = ""
		}
		if err := f.save(ctx, run); err != nil {
			spanErr = err
			return run, err
		}
	}

	run.Status = "done"
	if err := f.save(ctx, run); err != nil {
		spanErr = err
		return run, err
	}
	if f.opts.DeleteOnSuccess && f.checkpoint != nil {
		if err := f.checkpoint.Delete(ctx, run.ID); err != nil {
			spanErr = err
			return run, err
		}
	}
	f.record(resultFromRun(f.opts.TriggerTopic, run))
	f.log.Logf(logger.InfoLevel, "Flow %s run %s completed (%d steps)", f.name, run.ID, len(steps))
	return run, nil
}

// runStep runs one step, retrying on error up to the resolved retry count.
// A step with no Run function is a configuration error, and a canceled run
// stops retrying immediately rather than burning the rest of its budget.
func (f *Flow) runStep(ctx context.Context, step Step, in State) (State, int, Verification, error) {
	if step.Run == nil {
		return in, 0, Verification{}, fmt.Errorf("flow: step %q has no Run function", step.Name)
	}
	retries := f.opts.Retry
	if step.Retry > 0 {
		retries = step.Retry
	}
	var lastErr error
	var lastVerification Verification
	var feedback string
	for attempt := 1; attempt <= retries+1; attempt++ {
		// Stop the moment the run's context is canceled or its deadline
		// passes — a canceled run shouldn't keep retrying, and the context
		// error is surfaced so callers can detect cancellation upstream.
		if err := ctx.Err(); err != nil {
			return in, attempt - 1, lastVerification, err
		}
		attemptCtx := ctx
		if info, ok := ai.RunInfoFrom(ctx); ok {
			info.Step = step.Name
			info.VerificationFeedback = feedback
			attemptCtx = ai.WithRunInfo(ctx, info)
		}
		out, err := step.Run(attemptCtx, in)
		if err == nil && step.Verify != nil {
			lastVerification, err = step.Verify(attemptCtx, out)
			if err == nil && !lastVerification.Passed {
				err = &VerificationError{Step: step.Name, Feedback: lastVerification.Feedback}
			}
		}
		if err == nil {
			return out, attempt, lastVerification, nil
		}
		lastErr = err
		if verr, ok := err.(*VerificationError); ok {
			feedback = verr.Feedback
		}
		if attempt <= retries && f.opts.RetryBackoff > 0 {
			select {
			case <-time.After(f.opts.RetryBackoff):
			case <-ctx.Done():
				return in, attempt, lastVerification, ctx.Err()
			}
		}
	}
	return in, retries + 1, lastVerification, lastErr
}

func applyVerificationRecord(record *StepRecord, verification Verification) {
	if verification.Passed {
		record.VerificationStatus = "passed"
	}
	if verification.Feedback != "" {
		record.VerificationNote = truncate(verification.Feedback, 200)
		if !verification.Passed {
			record.VerificationStatus = "failed"
		}
	}
}

func (f *Flow) save(ctx context.Context, run Run) error {
	if f.checkpoint == nil {
		return nil
	}
	if err := f.checkpoint.Save(ctx, run); err != nil {
		f.log.Logf(logger.ErrorLevel, "Flow %s checkpoint save: %v", f.name, err)
		return fmt.Errorf("flow %s checkpoint save: %w", f.name, err)
	}
	return nil
}

func validateSteps(steps []Step) error {
	seen := make(map[string]struct{}, len(steps))
	for i, step := range steps {
		if step.Name == "" {
			return fmt.Errorf("flow: step %d has an empty name", i)
		}
		if _, ok := seen[step.Name]; ok {
			return fmt.Errorf("flow: duplicate step name %q", step.Name)
		}
		seen[step.Name] = struct{}{}
	}
	return nil
}

func stepIndex(steps []Step, name string) int {
	for i, s := range steps {
		if s.Name == name {
			return i
		}
	}
	return -1
}

func resultFromRun(trigger string, run Run) Result {
	r := Result{
		FlowName:  run.Flow,
		Trigger:   trigger,
		Timestamp: run.Started,
		Duration:  run.Updated.Sub(run.Started).Seconds(),
	}
	for _, s := range run.Steps {
		r.ToolCalls = append(r.ToolCalls, s.Name+":"+s.Status)
		if s.Error != "" {
			r.Error = s.Error
			r.ErrorKind = s.ErrorKind
		}
	}
	if run.Status == "done" {
		r.Answer = run.State.String()
	}
	return r
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "…"
}
