package persistence

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"
)

// RuntimeApprovalDefinitionAnchor is the minimal live identity retained by
// approval runtime state. It deliberately excludes every executable metadata
// field; immutable Catalog projections own those semantics.
type RuntimeApprovalDefinitionAnchor struct {
	ID            string
	ObjectAPIName string
	DeveloperName string
}

// RuntimeApprovalNodeAnchor is the minimal live identity retained by approval
// work items and histories. Labels, routing, criteria, and actions are absent
// because Catalog owns them.
type RuntimeApprovalNodeAnchor struct {
	ID                  string
	ProcessDefinitionID string
	DeveloperName       string
	StepOrder           int
}

type RuntimeApprovalIdentityAnchorRepository struct {
	db           *sql.DB
	transactions *TransactionManager
}

func NewRuntimeApprovalIdentityAnchorRepository(
	db *sql.DB,
	transactions *TransactionManager,
) *RuntimeApprovalIdentityAnchorRepository {
	return &RuntimeApprovalIdentityAnchorRepository{db: db, transactions: transactions}
}

func (r *RuntimeApprovalIdentityAnchorRepository) FindDefinitionByID(
	ctx context.Context,
	id string,
) ([]RuntimeApprovalDefinitionAnchor, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, errors.New("runtime Approval definition identity is empty")
	}
	return r.queryDefinitions(ctx, `SELECT Id, TableEnumOrId, DeveloperName
FROM ProcessDefinition
WHERE Id = ?
LIMIT 2`, id)
}

func (r *RuntimeApprovalIdentityAnchorRepository) FindDefinitionByNaturalKey(
	ctx context.Context,
	objectAPIName string,
	developerName string,
) ([]RuntimeApprovalDefinitionAnchor, error) {
	objectAPIName = strings.TrimSpace(objectAPIName)
	developerName = strings.TrimSpace(developerName)
	if objectAPIName == "" || developerName == "" {
		return nil, errors.New("runtime Approval definition natural identity is incomplete")
	}
	return r.queryDefinitions(ctx, `SELECT Id, TableEnumOrId, DeveloperName
FROM ProcessDefinition
WHERE TableEnumOrId = ? AND DeveloperName = ?
LIMIT 2`, objectAPIName, developerName)
}

func (r *RuntimeApprovalIdentityAnchorRepository) FindNodeByID(
	ctx context.Context,
	id string,
) ([]RuntimeApprovalNodeAnchor, error) {
	id = strings.TrimSpace(id)
	if id == "" {
		return nil, errors.New("runtime Approval node identity is empty")
	}
	return r.queryNodes(ctx, `SELECT Id, ProcessDefinitionId, DeveloperName, StepOrder
FROM ProcessNode
WHERE Id = ? AND IsDeleted = 0
LIMIT 2`, id)
}

func (r *RuntimeApprovalIdentityAnchorRepository) ListNodesByDefinitionID(
	ctx context.Context,
	definitionID string,
) ([]RuntimeApprovalNodeAnchor, error) {
	definitionID = strings.TrimSpace(definitionID)
	if definitionID == "" {
		return nil, errors.New("runtime Approval node definition identity is empty")
	}
	return r.queryNodes(ctx, `SELECT Id, ProcessDefinitionId, DeveloperName, StepOrder
FROM ProcessNode
WHERE ProcessDefinitionId = ? AND IsDeleted = 0
ORDER BY StepOrder ASC, Id ASC
LIMIT 101`, definitionID)
}

func (r *RuntimeApprovalIdentityAnchorRepository) queryDefinitions(
	ctx context.Context,
	query string,
	args ...any,
) ([]RuntimeApprovalDefinitionAnchor, error) {
	executor, err := r.executor(ctx)
	if err != nil {
		return nil, err
	}
	rows, err := executor.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("query runtime Approval definition identity: %w", err)
	}
	defer rows.Close()
	var result []RuntimeApprovalDefinitionAnchor
	for rows.Next() {
		var value RuntimeApprovalDefinitionAnchor
		if err := rows.Scan(&value.ID, &value.ObjectAPIName, &value.DeveloperName); err != nil {
			return nil, fmt.Errorf("scan runtime Approval definition identity: %w", err)
		}
		result = append(result, value)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate runtime Approval definition identity: %w", err)
	}
	return result, nil
}

func (r *RuntimeApprovalIdentityAnchorRepository) queryNodes(
	ctx context.Context,
	query string,
	args ...any,
) ([]RuntimeApprovalNodeAnchor, error) {
	executor, err := r.executor(ctx)
	if err != nil {
		return nil, err
	}
	rows, err := executor.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("query runtime Approval node identity: %w", err)
	}
	defer rows.Close()
	var result []RuntimeApprovalNodeAnchor
	for rows.Next() {
		var value RuntimeApprovalNodeAnchor
		if err := rows.Scan(
			&value.ID,
			&value.ProcessDefinitionID,
			&value.DeveloperName,
			&value.StepOrder,
		); err != nil {
			return nil, fmt.Errorf("scan runtime Approval node identity: %w", err)
		}
		result = append(result, value)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate runtime Approval node identity: %w", err)
	}
	return result, nil
}

func (r *RuntimeApprovalIdentityAnchorRepository) executor(ctx context.Context) (Executor, error) {
	if r == nil || r.db == nil {
		return nil, errors.New("runtime Approval identity repository is unavailable")
	}
	if r.transactions != nil {
		if tx, ok := r.transactions.ExtractTx(ctx).(*sql.Tx); ok && tx != nil {
			return tx, nil
		}
	}
	return r.db, nil
}
