package teamworkflow

import (
	"context"
	"fmt"

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

func (store *Store) CreateGenerating(ctx context.Context, params CreateGeneratingParams) (CreateResult, error) {
	var result CreateResult
	err := withTx(ctx, store, func(txStore *TxStore) error {
		var err error
		result, err = txStore.CreateGenerating(ctx, params)
		return err
	})
	return result, err
}

func (store *TxStore) CreateGenerating(ctx context.Context, params CreateGeneratingParams) (CreateResult, error) {
	if err := store.validate(); err != nil {
		return CreateResult{}, err
	}
	normalized, err := normalizeCreateGenerating(params)
	if err != nil {
		return CreateResult{}, err
	}
	if existing, found, err := loadBySourceMessage(ctx, store.tx, normalized.WorkspaceID, normalized.TeamID, normalized.SourceMessageID, true); err != nil {
		return CreateResult{}, err
	} else if found {
		return replayGenerating(existing, normalized)
	}
	if normalized.ID == "" {
		normalized.ID, err = newTeamWorkflowID()
		if err != nil {
			return CreateResult{}, err
		}
	}
	_, err = store.tx.Exec(ctx, `
INSERT INTO workspace_team_workflows (
  id, workspace_id, team_id, plan_id, revision, authoring_state,
  source_scope_id, source_message_id, requested_by_member_id,
  proposal_sequence, resolution_sequence,
  last_error_code, last_error_message, activated_at,
  deleted_by_member_id, deleted_at, created_at, updated_at
) VALUES ($1,$2,$3,NULL,1,'generating',$4,$5,$6,0,0,'','',NULL,NULL,NULL,$7,$7)
`, normalized.ID, normalized.WorkspaceID, normalized.TeamID, normalized.SourceScopeID,
		normalized.SourceMessageID, normalized.RequestedByMemberID, normalized.Now)
	if err != nil {
		if coredb.IsUniqueViolation(err) {
			existing, found, loadErr := loadBySourceMessage(ctx, store.tx, normalized.WorkspaceID, normalized.TeamID, normalized.SourceMessageID, true)
			if loadErr != nil {
				return CreateResult{}, loadErr
			}
			if found {
				return replayGenerating(existing, normalized)
			}
			return CreateResult{}, storeerr.Conflict("Team Workflow already exists")
		}
		return CreateResult{}, fmt.Errorf("insert generating Team Workflow: %w", err)
	}
	created, err := loadTeamWorkflow(ctx, store.tx, normalized.WorkspaceID, normalized.TeamID, normalized.ID, false, false)
	if err != nil {
		return CreateResult{}, err
	}
	return CreateResult{TeamWorkflow: created}, nil
}

func normalizeCreateGenerating(params CreateGeneratingParams) (CreateGeneratingParams, error) {
	var err error
	if params.ID != "" {
		params.ID, err = required(params.ID, "Team Workflow id", 64)
		if err != nil {
			return CreateGeneratingParams{}, err
		}
	}
	params.WorkspaceID, err = required(params.WorkspaceID, "workspaceId", 255)
	if err != nil {
		return CreateGeneratingParams{}, err
	}
	params.TeamID, err = required(params.TeamID, "teamId", 255)
	if err != nil {
		return CreateGeneratingParams{}, err
	}
	params.SourceScopeID, err = required(params.SourceScopeID, "sourceScopeId", 255)
	if err != nil {
		return CreateGeneratingParams{}, err
	}
	params.SourceMessageID, err = required(params.SourceMessageID, "sourceMessageId", 255)
	if err != nil {
		return CreateGeneratingParams{}, err
	}
	params.RequestedByMemberID, err = required(params.RequestedByMemberID, "requestedByMemberId", 255)
	if err != nil {
		return CreateGeneratingParams{}, err
	}
	params.Now = utcOrNow(params.Now)
	return params, nil
}

func replayGenerating(existing TeamWorkflow, params CreateGeneratingParams) (CreateResult, error) {
	if existing.SourceScopeID != params.SourceScopeID || existing.RequestedByMemberID != params.RequestedByMemberID {
		return CreateResult{}, storeerr.Conflict("Team Workflow command id was reused with another request")
	}
	return CreateResult{TeamWorkflow: existing, Replayed: true}, nil
}

func (store *Store) BindPlan(ctx context.Context, params BindPlanParams) (TeamWorkflow, error) {
	var result TeamWorkflow
	err := withTx(ctx, store, func(txStore *TxStore) error {
		var err error
		result, err = txStore.BindPlan(ctx, params)
		return err
	})
	return result, err
}

func (store *TxStore) BindPlan(ctx context.Context, params BindPlanParams) (TeamWorkflow, error) {
	if err := store.validate(); err != nil {
		return TeamWorkflow{}, err
	}
	workspaceID, teamID, id, err := normalizeIdentity(params.WorkspaceID, params.TeamID, params.TeamWorkflowID)
	if err != nil {
		return TeamWorkflow{}, err
	}
	planID, err := required(params.PlanID, "planId", 64)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := positiveRevision(params.ExpectedRevision, "expectedRevision"); err != nil {
		return TeamWorkflow{}, err
	}
	current, err := loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, true, true)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireRevision(current, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireNotDeleted(current); err != nil {
		return TeamWorkflow{}, err
	}
	if current.PlanID != nil {
		return TeamWorkflow{}, storeerr.Conflict("Team Workflow already has a Plan")
	}
	if current.AuthoringState != AuthoringStateGenerating && current.AuthoringState != AuthoringStateFailed {
		return TeamWorkflow{}, storeerr.Conflict("Team Workflow cannot bind a Plan in its current state")
	}
	now := utcOrNow(params.Now)
	if _, err := store.tx.Exec(ctx, `
UPDATE workspace_team_workflows
SET plan_id = $4, revision = revision + 1, authoring_state = 'generating',
    last_error_code = '', last_error_message = '', updated_at = $5
WHERE workspace_id = $1 AND team_id = $2 AND id = $3 AND revision = $6 AND deleted_at IS NULL
`, workspaceID, teamID, id, planID, now, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, fmt.Errorf("bind Team Workflow Plan: %w", mapWriteError(err))
	}
	return loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, false, false)
}

// MarkPublished transitions a relation to its directly published idle state.
// The caller must have already committed the immutable Plan version in the
// same transaction; no Approval row or frozen proposal is involved.
func (store *Store) MarkPublished(ctx context.Context, params MarkPublishedParams) (TeamWorkflow, error) {
	var result TeamWorkflow
	err := withTx(ctx, store, func(txStore *TxStore) error {
		var err error
		result, err = txStore.MarkPublished(ctx, params)
		return err
	})
	return result, err
}

func (store *TxStore) MarkPublished(ctx context.Context, params MarkPublishedParams) (TeamWorkflow, error) {
	if err := store.validate(); err != nil {
		return TeamWorkflow{}, err
	}
	workspaceID, teamID, id, err := normalizeIdentity(params.WorkspaceID, params.TeamID, params.TeamWorkflowID)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := positiveRevision(params.ExpectedRevision, "expectedRevision"); err != nil {
		return TeamWorkflow{}, err
	}
	current, err := loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, true, true)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireRevision(current, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireNotDeleted(current); err != nil {
		return TeamWorkflow{}, err
	}
	if current.PlanID == nil {
		return TeamWorkflow{}, storeerr.Conflict("Team Workflow must be bound to a Plan before publishing")
	}
	state := current.AuthoringState
	if state != AuthoringStateGenerating && state != AuthoringStateFailed && state != AuthoringStateIdle {
		return TeamWorkflow{}, storeerr.Conflict("Team Workflow cannot publish in its current state")
	}
	now := utcOrNow(params.Now)
	if _, err := store.tx.Exec(ctx, `
UPDATE workspace_team_workflows
SET revision = revision + 1, authoring_state = 'idle',
    last_error_code = '', last_error_message = '',
    activated_at = COALESCE(activated_at, $4), updated_at = $4
WHERE workspace_id = $1 AND team_id = $2 AND id = $3
  AND revision = $5 AND deleted_at IS NULL
`, workspaceID, teamID, id, now, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, fmt.Errorf("mark Team Workflow published: %w", mapWriteError(err))
	}
	return loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, false, false)
}

// AdvanceAvailabilityRevision keeps the relation CAS and list ordering in step
// with an archive/restore mutation on its linked Plan. Availability itself
// remains derived from the Plan row.
func (store *TxStore) AdvanceAvailabilityRevision(ctx context.Context, params AdvanceAvailabilityRevisionParams) (TeamWorkflow, error) {
	if err := store.validate(); err != nil {
		return TeamWorkflow{}, err
	}
	workspaceID, teamID, id, err := normalizeIdentity(params.WorkspaceID, params.TeamID, params.TeamWorkflowID)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := positiveRevision(params.ExpectedRevision, "expectedRevision"); err != nil {
		return TeamWorkflow{}, err
	}
	current, err := loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, true, true)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireRevision(current, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, err
	}
	if err := requireNotDeleted(current); err != nil {
		return TeamWorkflow{}, err
	}
	if current.PlanID == nil || current.AuthoringState != AuthoringStateIdle {
		return TeamWorkflow{}, storeerr.Conflict("only an established idle Team Workflow may change availability")
	}
	now := utcOrNow(params.Now)
	result, err := store.tx.Exec(ctx, `
UPDATE workspace_team_workflows
SET revision = revision + 1, updated_at = $4
WHERE workspace_id = $1 AND team_id = $2 AND id = $3
  AND revision = $5 AND deleted_at IS NULL AND authoring_state = 'idle'
`, workspaceID, teamID, id, now, params.ExpectedRevision)
	if err != nil {
		return TeamWorkflow{}, fmt.Errorf("advance Team Workflow availability revision: %w", mapWriteError(err))
	}
	if result.RowsAffected() == 0 {
		return TeamWorkflow{}, RevisionConflictError{Expected: params.ExpectedRevision, Actual: current.Revision}
	}
	return loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, false, false)
}

func (store *Store) SoftDelete(ctx context.Context, params SoftDeleteParams) (TeamWorkflow, error) {
	var result TeamWorkflow
	err := withTx(ctx, store, func(txStore *TxStore) error {
		var err error
		result, err = txStore.SoftDelete(ctx, params)
		return err
	})
	return result, err
}

func (store *TxStore) SoftDelete(ctx context.Context, params SoftDeleteParams) (TeamWorkflow, error) {
	if err := store.validate(); err != nil {
		return TeamWorkflow{}, err
	}
	workspaceID, teamID, id, err := normalizeIdentity(params.WorkspaceID, params.TeamID, params.TeamWorkflowID)
	if err != nil {
		return TeamWorkflow{}, err
	}
	deletedBy, err := required(params.DeletedByMemberID, "deletedByMemberId", 255)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if err := positiveRevision(params.ExpectedRevision, "expectedRevision"); err != nil {
		return TeamWorkflow{}, err
	}
	current, err := loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, true, true)
	if err != nil {
		return TeamWorkflow{}, err
	}
	if current.DeletedAt != nil {
		return current, nil
	}
	if err := requireRevision(current, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, err
	}
	now := utcOrNow(params.Now)
	if _, err := store.tx.Exec(ctx, `
UPDATE workspace_team_workflows
SET revision = revision + 1, deleted_by_member_id = $4, deleted_at = $5, updated_at = $5
WHERE workspace_id = $1 AND team_id = $2 AND id = $3 AND revision = $6 AND deleted_at IS NULL
`, workspaceID, teamID, id, deletedBy, now, params.ExpectedRevision); err != nil {
		return TeamWorkflow{}, fmt.Errorf("soft-delete Team Workflow: %w", mapWriteError(err))
	}
	return loadTeamWorkflow(ctx, store.tx, workspaceID, teamID, id, false, true)
}

func normalizeIdentity(workspaceID, teamID, id string) (string, string, string, error) {
	workspaceID, err := required(workspaceID, "workspaceId", 255)
	if err != nil {
		return "", "", "", err
	}
	teamID, err = required(teamID, "teamId", 255)
	if err != nil {
		return "", "", "", err
	}
	id, err = required(id, "teamWorkflowId", 64)
	if err != nil {
		return "", "", "", err
	}
	return workspaceID, teamID, id, nil
}

func requireRevision(current TeamWorkflow, expected int64) error {
	if current.Revision != expected {
		return RevisionConflictError{Expected: expected, Actual: current.Revision}
	}
	return nil
}

func requireNotDeleted(current TeamWorkflow) error {
	if current.DeletedAt != nil {
		return storeerr.Conflict("Team Workflow is deleted")
	}
	return nil
}
