package permissions

import (
	"context"
	"errors"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/nexuscrm/kernel/pkg/ports"
)

const (
	defaultPermissionAuthoringConvergenceLease = 30 * time.Second
	defaultPermissionAuthoringConvergenceRetry = 5 * time.Second
	defaultPermissionAuthoringConvergenceBatch = 100
)

// PermissionAuthoringConvergenceHandler performs the idempotent post-commit
// cache/UI projection work for an applied aggregate. Permission State is the
// authorization correctness floor; this handler controls convergence latency.
type PermissionAuthoringConvergenceHandler interface {
	ConvergePermissionAuthoring(ctx context.Context, event ports.PermissionAuthoringConvergenceEvent) error
}

type PermissionAuthoringRevisionReadback interface {
	ReadPermissionSetAuthoringRevision(context.Context, string) (uint64, bool, error)
}

// PermissionAuthoringReadbackConvergence is the durable recovery action for
// the aggregate's current L0 projections. Permission State already supplies
// the fail-closed authorization floor; acknowledgement is allowed only after a
// database read proves the committed aggregate revision is visible. A later
// revision also proves the requested commit and keeps delivery idempotent.
type PermissionAuthoringReadbackConvergence struct {
	revisions PermissionAuthoringRevisionReadback
}

func NewPermissionAuthoringReadbackConvergence(
	revisions PermissionAuthoringRevisionReadback,
) (*PermissionAuthoringReadbackConvergence, error) {
	if revisions == nil {
		return nil, errors.New("permission authoring read-back convergence requires the revision authority")
	}
	return &PermissionAuthoringReadbackConvergence{revisions: revisions}, nil
}

func (handler *PermissionAuthoringReadbackConvergence) ConvergePermissionAuthoring(
	ctx context.Context,
	event ports.PermissionAuthoringConvergenceEvent,
) error {
	if handler == nil || handler.revisions == nil || strings.TrimSpace(event.CanonicalPermissionSet) == "" ||
		event.AuthoringRevision == 0 {
		return errors.New("permission authoring read-back convergence event is incomplete")
	}
	revision, found, err := handler.revisions.ReadPermissionSetAuthoringRevision(
		ctx,
		event.CanonicalPermissionSet,
	)
	if err != nil {
		return err
	}
	if !found {
		return fmt.Errorf("permission authoring read-back target %q is unavailable", event.CanonicalPermissionSet)
	}
	if revision < event.AuthoringRevision {
		return fmt.Errorf(
			"permission authoring revision read-back %d is behind committed revision %d",
			revision,
			event.AuthoringRevision,
		)
	}
	return nil
}

type PermissionAuthoringConvergenceWorkerOptions struct {
	LeaseDuration time.Duration
	RetryDelay    time.Duration
	BatchSize     int
}

type PermissionAuthoringConvergenceWorker struct {
	store         ports.PermissionAuthoringConvergenceStore
	handler       PermissionAuthoringConvergenceHandler
	leaseDuration time.Duration
	retryDelay    time.Duration
	batchSize     int
}

// PermissionAuthoringConvergenceBatchResult separates durable work claimed by
// a pass from work finalized by successfully acknowledging its queue row.
// Retried work remains durable and is therefore claimed but not finalized.
type PermissionAuthoringConvergenceBatchResult struct {
	Claimed   int
	Finalized int
}

func NewPermissionAuthoringConvergenceWorker(
	store ports.PermissionAuthoringConvergenceStore,
	handler PermissionAuthoringConvergenceHandler,
	opts PermissionAuthoringConvergenceWorkerOptions,
) (*PermissionAuthoringConvergenceWorker, error) {
	if store == nil || handler == nil {
		return nil, errors.New("permission authoring convergence worker dependencies are incomplete")
	}
	if opts.LeaseDuration <= 0 {
		opts.LeaseDuration = defaultPermissionAuthoringConvergenceLease
	}
	if opts.RetryDelay < 0 {
		return nil, errors.New("permission authoring convergence retry delay cannot be negative")
	}
	if opts.RetryDelay == 0 {
		opts.RetryDelay = defaultPermissionAuthoringConvergenceRetry
	}
	if opts.BatchSize <= 0 {
		opts.BatchSize = defaultPermissionAuthoringConvergenceBatch
	}
	if opts.BatchSize > 1000 {
		return nil, errors.New("permission authoring convergence batch exceeds maximum")
	}
	return &PermissionAuthoringConvergenceWorker{
		store: store, handler: handler, leaseDuration: opts.LeaseDuration,
		retryDelay: opts.RetryDelay, batchSize: opts.BatchSize,
	}, nil
}

// RunBatch claims one bounded batch and drives every item to acknowledged
// deletion or back to an available PENDING disposition. A fresh lease token is
// derived from the stable worker ID so a restarted process cannot consume an
// older live lease.
func (worker *PermissionAuthoringConvergenceWorker) RunBatch(
	ctx context.Context,
	workerID string,
) (PermissionAuthoringConvergenceBatchResult, error) {
	workerID = strings.TrimSpace(workerID)
	if worker == nil || worker.store == nil || worker.handler == nil || workerID == "" || len(workerID) > 200 {
		return PermissionAuthoringConvergenceBatchResult{}, errors.New("permission authoring convergence worker is unavailable")
	}
	leaseOwner := workerID + ":" + uuid.NewString()
	work, err := worker.store.ClaimPermissionAuthoringConvergence(
		ctx, leaseOwner, worker.leaseDuration, worker.batchSize,
	)
	if err != nil {
		return PermissionAuthoringConvergenceBatchResult{}, err
	}
	result := PermissionAuthoringConvergenceBatchResult{Claimed: len(work)}
	var failures []error
	for _, item := range work {
		if err := worker.handler.ConvergePermissionAuthoring(ctx, item.Event); err != nil {
			retryErr := worker.store.RetryPermissionAuthoringConvergence(
				ctx, item.Event.CommandRowID, leaseOwner, err.Error(), worker.retryDelay,
			)
			if retryErr != nil {
				failures = append(failures, fmt.Errorf("convergence failed: %v; persist retry: %w", err, retryErr))
			} else {
				failures = append(failures, err)
			}
			continue
		}
		if err := worker.store.AcknowledgePermissionAuthoringConvergence(
			ctx, item.Event.CommandRowID, leaseOwner,
		); err != nil {
			failures = append(failures, err)
			continue
		}
		result.Finalized++
	}
	return result, errors.Join(failures...)
}
