// Package channeladmin holds the durable idempotency receipt store for
// direct Channel Exec mutations (`agentteam exec channel`). A receipt is a terminal,
// success-only replay record: (workspace, applicantAgent, requestId) is the
// idempotency key, request_digest distinguishes conflicting payloads, and
// result_json enables exact replay. It is written in the same transaction as
// the channel mutation so a crash rolls both back.
package channeladmin

import (
	"context"
	"fmt"
	"time"

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

// Receipt is one terminal Channel Exec mutation receipt.
type Receipt struct {
	WorkspaceID      string
	ApplicantAgentID string
	RequestID        string
	Action           string
	RequestDigest    []byte // SHA-256, 32 bytes
	ResultJSON       string
	CreatedAt        time.Time
}

// LookupReceipt returns a previously recorded success receipt for the
// idempotency key, if any. It reads through the caller's QueryExecer so the
// service can look up within the same transaction that will record the receipt.
func LookupReceipt(
	ctx context.Context,
	queryer coredb.QueryExecer,
	workspaceID string,
	applicantAgentID string,
	requestID string,
) (Receipt, bool, error) {
	row := queryer.QueryRow(ctx, `
SELECT action, request_digest, result_json, created_at
FROM channel_operation_receipts
WHERE workspace_id = $1
  AND applicant_agent_id = $2
  AND request_id = $3
`, workspaceID, applicantAgentID, requestID)
	var (
		receipt    Receipt
		digest     []byte
		action     string
		resultJSON string
		createdAt  time.Time
	)
	if err := row.Scan(&action, &digest, &resultJSON, &createdAt); err != nil {
		if coredb.IsNotFound(err) {
			return Receipt{}, false, nil
		}
		return Receipt{}, false, fmt.Errorf("lookup channel operation receipt: %w", err)
	}
	receipt = Receipt{
		WorkspaceID: workspaceID, ApplicantAgentID: applicantAgentID, RequestID: requestID,
		Action: action, RequestDigest: digest, ResultJSON: resultJSON, CreatedAt: createdAt,
	}
	return receipt, true, nil
}

// RecordReceipt reserves the idempotency key by inserting the receipt row. In
// the receipt-first flow it is called BEFORE the mutation with a placeholder
// result_json; SetReceiptResult later writes the real result in the same
// transaction. A duplicate-key error means the key is already committed (prior
// sequential replay or a concurrent winner); the caller rolls back and performs
// a read-only lookup to replay or conflict.
func RecordReceipt(ctx context.Context, execer coredb.QueryExecer, receipt Receipt) error {
	_, err := execer.Exec(ctx, `
INSERT INTO channel_operation_receipts (
  workspace_id, applicant_agent_id, request_id, action, request_digest, result_json, created_at
) VALUES ($1, $2, $3, $4, $5, $6, $7)
`, receipt.WorkspaceID, receipt.ApplicantAgentID, receipt.RequestID,
		receipt.Action, receipt.RequestDigest, receipt.ResultJSON, receipt.CreatedAt)
	if err != nil {
		return fmt.Errorf("record channel operation receipt: %w", err)
	}
	return nil
}

// SetReceiptResult writes the canonical success result into a previously
// reserved receipt row, within the same transaction that performed the
// mutation. The placeholder row is only visible once the transaction commits.
// RowsAffected must be exactly 1 (the reservation row written earlier in this
// transaction); otherwise the receipt/result same-transaction invariant is
// broken and the call fails closed.
func SetReceiptResult(ctx context.Context, execer coredb.QueryExecer, workspaceID string, applicantAgentID string, requestID string, resultJSON string) error {
	result, err := execer.Exec(ctx, `
UPDATE channel_operation_receipts
SET result_json = $4
WHERE workspace_id = $1
  AND applicant_agent_id = $2
  AND request_id = $3
`, workspaceID, applicantAgentID, requestID, resultJSON)
	if err != nil {
		return fmt.Errorf("set channel operation receipt result: %w", err)
	}
	if result.RowsAffected() != 1 {
		return fmt.Errorf("set channel operation receipt result: expected 1 reserved row, updated %d", result.RowsAffected())
	}
	return nil
}
