package runtime

import (
	"context"
	"fmt"
	"log/slog"
	"net/url"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/store/automation"
)

// AutomationCommitStatusRequest is the narrow request shape for writing an AGS
// commit status from the automation repo-status outbox.
type AutomationCommitStatusRequest struct {
	RepositoryOwner string
	RepositoryName  string
	CommitSHA       string
	State           string
	Context         string
	Description     string
	TargetURL       string
}

// AutomationRepoStatusWriter writes automation run state to a repository commit.
type AutomationRepoStatusWriter interface {
	CreateCommitStatus(ctx context.Context, req AutomationCommitStatusRequest) error
}

// AutomationRepoStatusOutboxWorkerConfig configures leasing and target links for
// automation repo-status delivery.
type AutomationRepoStatusOutboxWorkerConfig struct {
	LeaseOwner    string
	LeaseDuration time.Duration
	TargetBaseURL string
	Now           func() time.Time
}

// AutomationRepoStatusOutboxWorker delivers MeshX automation run status to AGS
// commit statuses through the automation repo-status outbox.
type AutomationRepoStatusOutboxWorker struct {
	store          *automation.Store
	writer         AutomationRepoStatusWriter
	leaseOwner     string
	leaseDuration  time.Duration
	targetBaseURL  string
	now            func() time.Time
	logger         *slog.Logger
	workspaceScope func(context.Context) ([]string, error)
}

// AutomationRepoStatusOutboxWorkerResult summarizes one outbox delivery pass.
type AutomationRepoStatusOutboxWorkerResult struct {
	Leased    int
	Delivered int
	Retried   int
	Failed    int
	Stale     int
}

// NewAutomationRepoStatusOutboxWorker creates a worker for delivering automation
// repo-status outbox rows.
func NewAutomationRepoStatusOutboxWorker(store *automation.Store, writer AutomationRepoStatusWriter, cfg AutomationRepoStatusOutboxWorkerConfig, logger *slog.Logger) *AutomationRepoStatusOutboxWorker {
	leaseOwner := strings.TrimSpace(cfg.LeaseOwner)
	if leaseOwner == "" {
		leaseOwner = "meshx-automation-repo-status-worker"
	}
	leaseDuration := cfg.LeaseDuration
	if leaseDuration <= 0 {
		leaseDuration = time.Minute
	}
	return &AutomationRepoStatusOutboxWorker{
		store:         store,
		writer:        writer,
		leaseOwner:    leaseOwner,
		leaseDuration: leaseDuration,
		targetBaseURL: strings.TrimRight(strings.TrimSpace(cfg.TargetBaseURL), "/"),
		now:           cfg.Now,
		logger:        logger,
	}
}

// SetWorkspaceScope installs the workspace-profile ownership resolver before
// the worker starts. All-profile runtimes leave it nil.
func (w *AutomationRepoStatusOutboxWorker) SetWorkspaceScope(scope func(context.Context) ([]string, error)) {
	if w == nil {
		return
	}
	w.workspaceScope = scope
}

// RunOnce leases and delivers at most limit due automation repo-status rows.
func (w *AutomationRepoStatusOutboxWorker) RunOnce(ctx context.Context, limit int) (AutomationRepoStatusOutboxWorkerResult, error) {
	if w == nil || w.store == nil {
		return AutomationRepoStatusOutboxWorkerResult{}, fmt.Errorf("automation repo status worker store is nil")
	}
	if w.writer == nil {
		return AutomationRepoStatusOutboxWorkerResult{}, fmt.Errorf("automation repo status writer is nil")
	}
	now := w.currentTime()
	var workspaceIDs []string
	var err error
	if w.workspaceScope != nil {
		workspaceIDs, err = w.workspaceScope(ctx)
		if err != nil {
			return AutomationRepoStatusOutboxWorkerResult{}, fmt.Errorf("load automation repo status workspace scope: %w", err)
		}
		if workspaceIDs == nil {
			workspaceIDs = []string{}
		}
	}
	rows, err := w.store.LeaseDueAutomationRepoStatuses(ctx, now, w.leaseOwner, now.Add(w.leaseDuration), limit, workspaceIDs)
	if err != nil {
		return AutomationRepoStatusOutboxWorkerResult{}, err
	}
	result := AutomationRepoStatusOutboxWorkerResult{Leased: len(rows)}
	for _, row := range rows {
		if err := w.deliver(ctx, row); err != nil {
			attempt, markErr := w.store.MarkAutomationRepoStatusAttemptFailed(ctx, row, w.leaseOwner, err, w.currentTime())
			if markErr != nil {
				return result, markErr
			}
			if attempt.Updated {
				if attempt.Failed {
					result.Failed++
				} else {
					result.Retried++
				}
			} else {
				result.Stale++
			}
			logger := w.logger
			if logger == nil {
				logger = slog.Default()
			}
			logger.Log(ctx, slog.LevelWarn, "automation repo status delivery failed", "run_id", row.RunID, "state", row.State, "attempts", row.Attempts+1, "error", err)
			continue
		}
		delivered, err := w.store.MarkAutomationRepoStatusDelivered(ctx, row, w.leaseOwner, w.currentTime())
		if err != nil {
			return result, err
		}
		if delivered {
			result.Delivered++
		} else {
			result.Stale++
		}
	}
	return result, nil
}

func (w *AutomationRepoStatusOutboxWorker) deliver(ctx context.Context, row automation.AutomationRepoStatusOutbox) error {
	return w.writer.CreateCommitStatus(ctx, AutomationCommitStatusRequest{
		RepositoryOwner: strings.TrimSpace(row.RepositoryOwner),
		RepositoryName:  strings.TrimSpace(row.RepositoryName),
		CommitSHA:       strings.TrimSpace(row.CommitSHA),
		State:           strings.TrimSpace(row.State),
		Context:         strings.TrimSpace(row.Context),
		Description:     strings.TrimSpace(row.Description),
		TargetURL:       w.targetURL(row.WorkspaceID, row.RunID),
	})
}

func (w *AutomationRepoStatusOutboxWorker) targetURL(workspaceID, runID string) string {
	if w.targetBaseURL == "" {
		return ""
	}
	workspaceID = strings.TrimSpace(workspaceID)
	runID = strings.TrimSpace(runID)
	if workspaceID == "" || runID == "" {
		return ""
	}
	return w.targetBaseURL + "/w/" + url.PathEscape(workspaceID) + "/automations/runs/" + url.PathEscape(runID)
}

func (w *AutomationRepoStatusOutboxWorker) currentTime() time.Time {
	if w.now != nil {
		return w.now()
	}
	return time.Now().UTC()
}
