package records

import (
	"context"
	"fmt"

	appErrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/persistence"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func createFinalImageRefreshRequired(schema *models.ObjectMetadata) bool {
	// RecordCreated payloads and create responses are outward-facing record
	// images. When a path can observe post-insert side effects, reload the
	// physical row once at the end of the transaction so the committed row
	// remains the single source of truth. Outbox presence alone is not such a
	// signal; ordinary creates can emit the already-prepared final image without
	// paying an extra physical read.
	if schema == nil {
		return false
	}
	if len(schema.DefaultChildRows) > 0 || len(schema.ChildCloneRules) > 0 {
		return true
	}
	return len(schema.RecordLifecycleCapabilitiesForPhase(string(recordLifecycleAfterCreateInTx))) > 0
}

func finalCreateImageReadContext(ctx context.Context) context.Context {
	return persistence.WithPhysicalReadOnly(constants.WithSecurityContext(
		ctx,
		constants.SystemContext,
		"final create image read",
	))
}

func recordIDForFinalCreateImage(record models.SObject) string {
	if record == nil {
		return ""
	}
	if id, ok := record[constants.FieldID].(string); ok {
		return id
	}
	if raw, ok := record[constants.FieldID]; ok && raw != nil {
		return fmt.Sprint(raw)
	}
	return ""
}

func (ps *PersistenceService) loadFinalCreateImage(ctx context.Context, objectName string, record models.SObject) (models.SObject, error) {
	id := recordIDForFinalCreateImage(record)
	if id == "" {
		return cloneSObject(record), nil
	}
	row, err := ps.repo.FindOne(finalCreateImageReadContext(ctx), objectName, id)
	if err != nil {
		return nil, appErrors.NewInternalError("failed to load final create image", err)
	}
	if row == nil {
		return nil, appErrors.NewInternalError(fmt.Sprintf("final create image missing for %s %s", objectName, id), nil)
	}
	return row, nil
}

func (ps *PersistenceService) loadFinalBulkCreateImages(ctx context.Context, objectName string, records []models.SObject, landedRows []bool) ([]models.SObject, error) {
	if len(records) == 0 {
		return records, nil
	}

	// Bulk keeps this as one FindMany over landed IDs, not one read per row.
	finalRecords := cloneBulkInsertSObjects(records)
	ids := make([]string, 0, len(records))
	indexesByID := make(map[string][]int, len(records))
	for i, record := range records {
		if i >= len(landedRows) || !landedRows[i] {
			continue
		}
		id := recordIDForFinalCreateImage(record)
		if id == "" {
			continue
		}
		if _, seen := indexesByID[id]; !seen {
			ids = append(ids, id)
		}
		indexesByID[id] = append(indexesByID[id], i)
	}
	if len(ids) == 0 {
		return finalRecords, nil
	}

	rows, err := ps.repo.FindMany(finalCreateImageReadContext(ctx), objectName, ids)
	if err != nil {
		return nil, appErrors.NewInternalError("failed to load final bulk create images", err)
	}
	for _, id := range ids {
		row := rows[id]
		if row == nil {
			return nil, appErrors.NewInternalError(fmt.Sprintf("final bulk create image missing for %s %s", objectName, id), nil)
		}
		for _, idx := range indexesByID[id] {
			finalRecords[idx] = cloneSObject(row)
		}
	}
	return finalRecords, nil
}
