package metadata

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

	"github.com/nexuscrm/automation/triggersub"
	"github.com/nexuscrm/shared/pkg/models"
	"golang.org/x/sync/singleflight"
)

type triggerSubscriptionRuntimeCache struct {
	mu        sync.RWMutex
	lists     map[string][]triggersub.Row
	hasAny    map[string]bool
	epoch     uint64
	loadGroup singleflight.Group
}

type triggerSubscriptionListLoader func(context.Context, triggersub.Executor, string, string, string) ([]triggersub.Row, error)

type triggerSubscriptionHasAnyLoader func(context.Context, triggersub.Executor, string) (bool, error)

func (ms *MetadataService) clearTriggerSubscriptionRuntimeCache() {
	if ms == nil {
		return
	}
	ms.triggerSubscriptionRuntime.mu.Lock()
	ms.triggerSubscriptionRuntime.lists = nil
	ms.triggerSubscriptionRuntime.hasAny = nil
	ms.triggerSubscriptionRuntime.epoch++
	ms.triggerSubscriptionRuntime.mu.Unlock()
}

func triggerSubscriptionListCacheKey(keyPrefix string, eventType string, recordTriggerType string) string {
	keyPrefix = strings.TrimSpace(keyPrefix)
	eventType = strings.TrimSpace(eventType)
	recordTriggerType = strings.TrimSpace(recordTriggerType)
	if keyPrefix == "" || eventType == "" || recordTriggerType == "" {
		return ""
	}
	return keyPrefix + "\x00" + eventType + "\x00" + recordTriggerType
}

func triggerSubscriptionHasAnyCacheKey(keyPrefix string) string {
	return strings.TrimSpace(keyPrefix)
}

func triggerSubscriptionLoadGroupKey(namespace string, epoch uint64, cacheKey string) string {
	return fmt.Sprintf("%s:%d:%s", namespace, epoch, cacheKey)
}

func cloneTriggerSubscriptionRows(rows []triggersub.Row) []triggersub.Row {
	if len(rows) == 0 {
		return nil
	}
	cloned := make([]triggersub.Row, len(rows))
	copy(cloned, rows)
	return cloned
}

func (ms *MetadataService) triggerSubscriptionReadExecutor(ctx context.Context) (triggersub.Executor, bool, error) {
	exec, err := ms.triggerSubscriptionExecutor(ctx)
	if err != nil {
		return nil, false, err
	}
	if ms != nil && ms.txManager != nil && ms.txManager.IsTransactionActive(ctx) {
		return exec, true, nil
	}
	if _, ok := exec.(*sql.Tx); ok {
		return exec, true, nil
	}
	return exec, false, nil
}

func (ms *MetadataService) listActiveTriggerSubscriptionsCached(ctx context.Context, keyPrefix string, eventType string, recordTriggerType string) ([]triggersub.Row, error) {
	cacheKey := triggerSubscriptionListCacheKey(keyPrefix, eventType, recordTriggerType)
	if cacheKey == "" {
		return nil, nil
	}
	loader := ms.triggerSubscriptionList
	if loader == nil {
		loader = triggersub.List
	}
	exec, txScoped, err := ms.triggerSubscriptionReadExecutor(ctx)
	if err != nil {
		return nil, err
	}
	if txScoped {
		rows, err := loader(ctx, exec, keyPrefix, eventType, recordTriggerType)
		if err != nil {
			return nil, err
		}
		return cloneTriggerSubscriptionRows(rows), nil
	}

	for {
		ms.triggerSubscriptionRuntime.mu.RLock()
		if ms.triggerSubscriptionRuntime.lists != nil {
			if rows, ok := ms.triggerSubscriptionRuntime.lists[cacheKey]; ok {
				ms.triggerSubscriptionRuntime.mu.RUnlock()
				return cloneTriggerSubscriptionRows(rows), nil
			}
		}
		loadEpoch := ms.triggerSubscriptionRuntime.epoch
		ms.triggerSubscriptionRuntime.mu.RUnlock()

		loadKey := triggerSubscriptionLoadGroupKey("list", loadEpoch, cacheKey)
		loaded, err, _ := ms.triggerSubscriptionRuntime.loadGroup.Do(loadKey, func() (interface{}, error) {
			rows, err := loader(ctx, exec, keyPrefix, eventType, recordTriggerType)
			if err != nil {
				return nil, err
			}
			return cloneTriggerSubscriptionRows(rows), nil
		})
		if err != nil {
			return nil, err
		}
		rows, _ := loaded.([]triggersub.Row)

		ms.triggerSubscriptionRuntime.mu.Lock()
		if ms.triggerSubscriptionRuntime.epoch != loadEpoch {
			ms.triggerSubscriptionRuntime.mu.Unlock()
			continue
		}
		if ms.triggerSubscriptionRuntime.lists == nil {
			ms.triggerSubscriptionRuntime.lists = make(map[string][]triggersub.Row)
		}
		if cached, ok := ms.triggerSubscriptionRuntime.lists[cacheKey]; ok {
			ms.triggerSubscriptionRuntime.mu.Unlock()
			return cloneTriggerSubscriptionRows(cached), nil
		}
		ms.triggerSubscriptionRuntime.lists[cacheKey] = cloneTriggerSubscriptionRows(rows)
		ms.triggerSubscriptionRuntime.mu.Unlock()
		return cloneTriggerSubscriptionRows(rows), nil
	}
}

func (ms *MetadataService) hasAnyActiveRecordTriggerSubscriptionCached(ctx context.Context, keyPrefix string) (bool, error) {
	cacheKey := triggerSubscriptionHasAnyCacheKey(keyPrefix)
	if cacheKey == "" {
		return false, nil
	}
	loader := ms.triggerSubscriptionHasAny
	if loader == nil {
		loader = defaultTriggerSubscriptionHasAnyLoader
	}
	exec, txScoped, err := ms.triggerSubscriptionReadExecutor(ctx)
	if err != nil {
		return false, err
	}
	if txScoped {
		return loader(ctx, exec, cacheKey)
	}

	for {
		ms.triggerSubscriptionRuntime.mu.RLock()
		if ms.triggerSubscriptionRuntime.hasAny != nil {
			if present, ok := ms.triggerSubscriptionRuntime.hasAny[cacheKey]; ok {
				ms.triggerSubscriptionRuntime.mu.RUnlock()
				return present, nil
			}
		}
		loadEpoch := ms.triggerSubscriptionRuntime.epoch
		ms.triggerSubscriptionRuntime.mu.RUnlock()

		loadKey := triggerSubscriptionLoadGroupKey("hasAny", loadEpoch, cacheKey)
		loaded, err, _ := ms.triggerSubscriptionRuntime.loadGroup.Do(loadKey, func() (interface{}, error) {
			return loader(ctx, exec, cacheKey)
		})
		if err != nil {
			return false, err
		}
		present, _ := loaded.(bool)

		ms.triggerSubscriptionRuntime.mu.Lock()
		if ms.triggerSubscriptionRuntime.epoch != loadEpoch {
			ms.triggerSubscriptionRuntime.mu.Unlock()
			continue
		}
		if ms.triggerSubscriptionRuntime.hasAny == nil {
			ms.triggerSubscriptionRuntime.hasAny = make(map[string]bool)
		}
		if cached, ok := ms.triggerSubscriptionRuntime.hasAny[cacheKey]; ok {
			ms.triggerSubscriptionRuntime.mu.Unlock()
			return cached, nil
		}
		ms.triggerSubscriptionRuntime.hasAny[cacheKey] = present
		ms.triggerSubscriptionRuntime.mu.Unlock()
		return present, nil
	}
}

func defaultTriggerSubscriptionHasAnyLoader(ctx context.Context, exec triggersub.Executor, cacheKey string) (bool, error) {
	var present int
	err := exec.QueryRowContext(ctx, `
		SELECT 1
		FROM _System_TriggerSubscription
		WHERE KeyPrefix COLLATE utf8mb4_bin = ?
		  AND IsActive = 1
		  AND EventType IN (?, ?, ?, ?)
		LIMIT 1`,
		cacheKey,
		"RecordBeforeSave",
		"RecordAfterSave",
		"RecordBeforeDelete",
		"RecordAfterDelete",
	).Scan(&present)
	if err == nil {
		return true, nil
	}
	if errors.Is(err, sql.ErrNoRows) {
		return false, nil
	}
	return false, fmt.Errorf("query active record trigger subscriptions for key prefix %s: %w", cacheKey, err)
}

// ListActiveTriggerSubscriptions exposes the TriggerSubscription runtime read
// path through MetadataService so runtime callers do not reach around the
// metadata layer for direct DB access.
func (ms *MetadataService) ListActiveTriggerSubscriptions(ctx context.Context, keyPrefix string, eventType string, recordTriggerType string) ([]triggersub.Row, error) {
	if ms != nil && ms.UsesRequestBoundRuntimeView() {
		view, err := ms.requireRequestBoundRuntimeMutationView(ctx, "record-trigger subscription metadata")
		if err != nil {
			return nil, err
		}
		values := view.TriggerSubscriptions(keyPrefix, eventType, recordTriggerType)
		out := make([]triggersub.Row, len(values))
		for i, value := range values {
			out[i] = triggersub.Row{Id: value.ID, KeyPrefix: value.KeyPrefix, EventType: value.EventType,
				RecordTriggerType: value.RecordTriggerType, FlowDefinitionId: value.FlowDefinitionID,
				FlowVersionId: value.FlowVersionID, IsActive: value.IsActive}
		}
		return out, nil
	}
	if ms == nil || ms.repo == nil {
		return nil, fmt.Errorf("trigger subscription runtime read requires metadata repository")
	}
	return ms.listActiveTriggerSubscriptionsCached(ctx, keyPrefix, eventType, recordTriggerType)
}

// GetRuntimeFlowDefinition returns a FlowDefinition for runtime resolver use
// without collapsing cache initialization failures into "missing definition".
func (ms *MetadataService) GetRuntimeFlowDefinition(ctx context.Context, id string) (*models.FlowDefinition, error) {
	if ms == nil {
		return nil, fmt.Errorf("runtime flow definition read requires metadata service")
	}
	if ms.UsesRequestBoundRuntimeView() {
		view, err := ms.requireRequestBoundRuntimeMutationView(ctx, "Flow definition execution metadata")
		if err != nil {
			return nil, err
		}
		definition, exists := view.FlowDefinition(id)
		if !exists {
			return nil, nil
		}
		return definition, nil
	}
	if err := ms.ensureCacheInitialized(ctx); err != nil {
		return nil, fmt.Errorf("initialize metadata cache for runtime flow definition read: %w", err)
	}
	snap := ms.loadCache()
	if snap == nil {
		return nil, fmt.Errorf("runtime flow definition read has no metadata cache snapshot")
	}
	return snap.flowDefinitionMap[id], nil
}

func (ms *MetadataService) hasAnyActiveRecordTriggerSubscription(ctx context.Context, keyPrefix string) (bool, error) {
	return ms.hasAnyActiveRecordTriggerSubscriptionCached(ctx, keyPrefix)
}
