package plan

import (
	"context"
	"fmt"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

type ArchivePlanParams struct {
	WorkspaceID                  string
	PlanID                       string
	ExpectedRevision             int64
	Actor                        Actor
	RequireNoActiveAgentEditWake bool
	Now                          time.Time
}

type RestorePlanParams struct {
	WorkspaceID      string
	PlanID           string
	ExpectedRevision int64
	Actor            Actor
	Now              time.Time
}

func (store *Store) ArchivePlan(ctx context.Context, params ArchivePlanParams) (Plan, error) {
	if err := store.Validate(); err != nil {
		return Plan{}, err
	}
	var archived Plan
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var txErr error
		archived, txErr = archivePlanInTx(ctx, tx, params)
		return txErr
	})
	return archived, err
}

// ArchivePlan archives one Plan inside the caller-owned transaction.
func (store *TxStore) ArchivePlan(ctx context.Context, params ArchivePlanParams) (Plan, error) {
	if err := store.validate(); err != nil {
		return Plan{}, err
	}
	return archivePlanInTx(ctx, store.tx, params)
}

func archivePlanInTx(ctx context.Context, tx coredb.Tx, params ArchivePlanParams) (Plan, error) {
	workspaceID, planID := trim(params.WorkspaceID), trim(params.PlanID)
	if workspaceID == "" || planID == "" || params.ExpectedRevision <= 0 {
		return Plan{}, storeerr.BadRequest("workspaceId, planId, and a positive expectedRevision are required")
	}
	actor, err := validateActor(params.Actor)
	if err != nil {
		return Plan{}, err
	}
	plan, err := loadPlan(ctx, tx, workspaceID, planID, true)
	if err != nil {
		return Plan{}, err
	}
	if err := requirePlanRevision(plan, params.ExpectedRevision); err != nil {
		return Plan{}, err
	}
	if plan.ArchivedAt != nil {
		return plan, nil
	}
	if params.RequireNoActiveAgentEditWake {
		if err := requireNoMatchingActiveAgentEditWake(
			ctx, tx, plan, actor,
		); err != nil {
			return Plan{}, err
		}
	}
	now := utcOrNow(params.Now)
	result, err := tx.Exec(ctx, `
UPDATE workflow_plans
SET revision = revision + 1,
    updated_by_member_id = $1,
    updated_by_actor_type = $2,
    updated_by_actor_id = $3,
    archived_by_member_id = $1,
    archived_by_actor_type = $2,
    archived_by_actor_id = $3,
    archived_at = $4,
    updated_at = $4
WHERE workspace_id = $5 AND id = $6 AND revision = $7 AND archived_at IS NULL
`, actor.MemberID, actor.Type, actor.ID, now, workspaceID, planID, params.ExpectedRevision)
	if err != nil {
		return Plan{}, fmt.Errorf("archive workflow plan: %w", mapWriteError(err))
	}
	if result.RowsAffected() == 0 {
		return Plan{}, RevisionConflictError{Resource: "plan", Expected: params.ExpectedRevision, Actual: plan.Revision}
	}
	return loadPlan(ctx, tx, workspaceID, planID, false)
}

func (store *Store) RestorePlan(ctx context.Context, params RestorePlanParams) (Plan, error) {
	if err := store.Validate(); err != nil {
		return Plan{}, err
	}
	var restored Plan
	err := coredb.WithTx(ctx, store.db, func(tx coredb.Tx) error {
		var txErr error
		restored, txErr = restorePlanInTx(ctx, tx, params)
		return txErr
	})
	return restored, err
}

// RestorePlan restores one Plan inside the caller-owned transaction.
func (store *TxStore) RestorePlan(ctx context.Context, params RestorePlanParams) (Plan, error) {
	if err := store.validate(); err != nil {
		return Plan{}, err
	}
	return restorePlanInTx(ctx, store.tx, params)
}

func restorePlanInTx(ctx context.Context, tx coredb.Tx, params RestorePlanParams) (Plan, error) {
	workspaceID, planID := trim(params.WorkspaceID), trim(params.PlanID)
	if workspaceID == "" || planID == "" || params.ExpectedRevision <= 0 {
		return Plan{}, storeerr.BadRequest("workspaceId, planId, and a positive expectedRevision are required")
	}
	actor, err := validateActor(params.Actor)
	if err != nil {
		return Plan{}, err
	}
	plan, err := loadPlan(ctx, tx, workspaceID, planID, true)
	if err != nil {
		return Plan{}, err
	}
	if err := requirePlanRevision(plan, params.ExpectedRevision); err != nil {
		return Plan{}, err
	}
	if plan.ArchivedAt == nil {
		return plan, nil
	}
	result, err := tx.Exec(ctx, `
UPDATE workflow_plans
SET revision = revision + 1,
    updated_by_member_id = $1,
    updated_by_actor_type = $2,
    updated_by_actor_id = $3,
    archived_by_member_id = NULL,
    archived_by_actor_type = NULL,
    archived_by_actor_id = NULL,
    archived_at = NULL,
    updated_at = $4
WHERE workspace_id = $5 AND id = $6 AND revision = $7 AND archived_at IS NOT NULL
`, actor.MemberID, actor.Type, actor.ID, utcOrNow(params.Now), workspaceID, planID, params.ExpectedRevision)
	if err != nil {
		return Plan{}, fmt.Errorf("restore workflow plan: %w", mapWriteError(err))
	}
	if result.RowsAffected() == 0 {
		return Plan{}, RevisionConflictError{Resource: "plan", Expected: params.ExpectedRevision, Actual: plan.Revision}
	}
	return loadPlan(ctx, tx, workspaceID, planID, false)
}
