package identityruntime

import (
	"context"
	"errors"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
	"github.com/easyai/easyai-ai-gateway/apps/api/internal/store"
)

type tenantContextSyncRepository interface {
	DueOIDCTenantBindingSyncs(context.Context, string, string, int) ([]store.OIDCTenantBindingSyncTarget, error)
	ApplyOIDCTenantBindingSync(context.Context, string, identity.TenantContext, bool, time.Time) error
	RejectOIDCTenantBinding(context.Context, string, string, time.Time) error
	FailOIDCTenantBindingSync(context.Context, string, string, time.Time) error
}

type tenantContextSyncReader interface {
	Get(context.Context, string, string) (identity.TenantContext, bool, error)
}

func runTenantContextSynchronizer(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string) {
	ticker := time.NewTicker(15 * time.Second)
	defer ticker.Stop()
	for {
		select {
		case <-ctx.Done():
			return
		case now := <-ticker.C:
			_ = synchronizeDueTenantContexts(ctx, repository, reader, issuer, applicationID, now.UTC())
		}
	}
}

func synchronizeDueTenantContexts(ctx context.Context, repository tenantContextSyncRepository, reader tenantContextSyncReader, issuer, applicationID string, now time.Time) error {
	targets, err := repository.DueOIDCTenantBindingSyncs(ctx, issuer, applicationID, 50)
	if err != nil {
		return err
	}
	for _, target := range targets {
		tenant, unchanged, readErr := reader.Get(ctx, target.ExternalTenantID, target.ETag)
		switch {
		case readErr == nil && unchanged:
			err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, identity.TenantContext{}, true, now)
		case readErr == nil && tenant.Active():
			err = repository.ApplyOIDCTenantBindingSync(ctx, target.ID, tenant, false, now)
		case readErr == nil:
			err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_inactive", now)
		case errors.Is(readErr, identity.ErrTenantContextNotFound):
			err = repository.RejectOIDCTenantBinding(ctx, target.ID, "tenant_not_found", now)
		default:
			err = repository.FailOIDCTenantBindingSync(
				ctx, target.ID, "tenant_context_unavailable", now.Add(tenantContextRetryDelay(target.FailureCount)),
			)
		}
		if err != nil && !errors.Is(err, store.ErrOIDCTenantUnavailable) {
			return err
		}
	}
	return nil
}

func tenantContextRetryDelay(failureCount int) time.Duration {
	if failureCount < 0 {
		failureCount = 0
	}
	delay := 30 * time.Second
	for index := 0; index < failureCount && delay < 5*time.Minute; index++ {
		delay *= 2
	}
	if delay > 5*time.Minute {
		return 5 * time.Minute
	}
	return delay
}
