package persistence

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"

	"github.com/nexuscrm/kernel/pkg/jsondocument"
	"github.com/rs/zerolog/log"
)

// MetadataRepository handles database operations for platform metadata.
// Scanner functions are split across domain-specific files:
//   - metadata_scan_schema.go — object, field, and aliasing scanners
//   - metadata_scan_ui.go     — app, layout, list view, theme, and dashboard scanners
//   - metadata_scan_logic.go  — action and validation rule scanners
//   - repo_actions.go         — action CRUD
//   - repo_apps.go            — app CRUD
//   - repo_dashboards.go    — dashboard CRUD
//   - repo_fields.go          — field CRUD
//   - repo_listview.go        — list view CRUD
//   - repo_theme.go           — theme CRUD
//   - repo_ui.go              — compact layout and related list CRUD
//   - repo_validations.go     — validation rule CRUD
type MetadataRepository struct {
	db                               *sql.DB
	permissionStateDirectCoordinator *PermissionStateDirectWriterCoordinator
}

func NewMetadataRepository(db *sql.DB) *MetadataRepository {
	return &MetadataRepository{db: db}
}

// SetPermissionStateDirectWriterCoordinator installs the production
// transaction owner for direct metadata repository writers.
func (r *MetadataRepository) SetPermissionStateDirectWriterCoordinator(
	coordinator *PermissionStateDirectWriterCoordinator,
) error {
	if r == nil || coordinator == nil {
		return errors.New("persistence: permission state direct writer coordinator is required")
	}
	r.permissionStateDirectCoordinator = coordinator
	return nil
}

// CheckLayoutAuthoringMutationReady proves that the metadata repository's
// direct-writer projection is bound to the exact transaction and Permission
// State authorities owned by the Layout authoring mutation composition. This
// is a construction-time check: a missing coordinator must never be discovered
// by the first ListFields mutation.
func (r *MetadataRepository) CheckLayoutAuthoringMutationReady(
	transactions *TransactionManager,
	authority *PermissionStateAuthority,
) error {
	if r == nil || transactions == nil || authority == nil ||
		r.permissionStateDirectCoordinator == nil ||
		r.permissionStateDirectCoordinator.transactions != transactions ||
		r.permissionStateDirectCoordinator.authority != authority {
		return errors.New("persistence: Layout authoring mutation repository authority is incomplete")
	}
	return nil
}

// Write Methods (Exec)

// Exec executes a query without returning result rows
func (r *MetadataRepository) Exec(ctx context.Context, query string, args ...interface{}) (sql.Result, error) {
	return r.db.ExecContext(ctx, query, args...)
}

func (r *MetadataRepository) marshalJSON(v interface{}) (string, error) {
	if v == nil {
		return "", nil
	}
	b, err := json.Marshal(v)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

// decodeJSONUseNumber is the one decoder for stored JSON documents whose
// numeric lexemes are authored facts. Ordinary json.Unmarshal irreversibly
// converts every number in interface-valued subtrees to float64.
func decodeJSONUseNumber(data string, target interface{}) error {
	return jsondocument.DecodeUseNumber([]byte(data), target)
}

func (r *MetadataRepository) unmarshalJSON(data string, v interface{}) {
	if data == "" {
		return
	}
	if err := json.Unmarshal([]byte(data), v); err != nil {
		log.Error().Err(err).Str("data", data).Msg("Failed to unmarshal JSON")
	}
}

// Scan Helpers (Local to Package)

type Scannable interface {
	Scan(dest ...interface{}) error
}

func (r *MetadataRepository) GetExecutor(ctx context.Context) Executor {
	if tx, ok := ctx.Value(txContextKey{}).(*sql.Tx); ok && tx != nil {
		return tx
	}
	return r.db
}

func (r *MetadataRepository) permissionStateDirectWriterShouldRun(
	ctx context.Context,
	writerID string,
) bool {
	if r == nil {
		return false
	}
	if permissionStateDirectWriterIsActive(ctx, writerID) {
		return false
	}
	if r.permissionStateDirectCoordinator == nil {
		return true
	}
	return r.permissionStateDirectCoordinator.shouldRun(ctx, writerID)
}

func (r *MetadataRepository) runPermissionStateDirectWriterGroup(
	ctx context.Context,
	mutation func(context.Context) error,
) error {
	coordinator := r.permissionStateDirectCoordinator
	if coordinator == nil {
		return errPermissionStateDirectWriterCoordinatorRequired
	}
	if coordinator.transactions.IsPessimisticTx(ctx) {
		return mutation(ctx)
	}
	return coordinator.transactions.WithPessimisticTransaction(ctx, mutation)
}

func (r *MetadataRepository) runPermissionStateDirectWriter(
	ctx context.Context,
	writerID string,
	targets []permissionStateDirectTarget,
	mutation func(context.Context) error,
) error {
	coordinator := r.permissionStateDirectCoordinator
	if coordinator == nil {
		return errPermissionStateDirectWriterCoordinatorRequired
	}
	if !coordinator.shouldRun(ctx, writerID) {
		return mutation(ctx)
	}
	return coordinator.runProjected(
		ctx,
		writerID,
		targets,
		r.GetExecutor,
		mutation,
	)
}

func (r *MetadataRepository) requirePermissionStateOwnedExecutor(
	ctx context.Context,
	exec Executor,
) error {
	coordinator := r.permissionStateDirectCoordinator
	if coordinator == nil {
		return errPermissionStateDirectWriterCoordinatorRequired
	}
	active, _ := coordinator.transactions.ExtractTx(ctx).(*sql.Tx)
	explicit, ok := exec.(*sql.Tx)
	if !ok || explicit == nil || active == nil || explicit != active ||
		!coordinator.transactions.IsPessimisticTx(ctx) {
		return errors.New(
			"persistence: direct metadata writer requires the coordinator-owned pessimistic transaction",
		)
	}
	return nil
}

// Schema loading leverages GetExecutor for proper transaction hygiene.
