package user

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

	"github.com/google/uuid"
)

// ActiveLimit is the read-side projection of a row in `limit_active`
// joined with the audit columns from the underlying `limit_records`
// row. It mirrors the OpenAPI `ActiveLimit` schema.
type ActiveLimit struct {
	LimitCode string
	Value int32
	ReasonCode string
	Actor ActorRef
	AppliedAt time.Time
	ExpiresAt *time.Time
}

// ApplyLimitInput carries the admin-supplied parameters of
// `POST /api/v1/admin/users/{user_id}/limits`.
type ApplyLimitInput struct {
	UserID uuid.UUID
	LimitCode string
	Value int32
	ReasonCode string
	Actor ActorRef
	ExpiresAt *time.Time
}

// ApplyLimit persists a fresh `limit_records` row and upserts
// `limit_active` in one transaction. The implementation keeps `limit_code` as an
// open string; The implementation may add a CHECK constraint once the closed
// set is locked in.
func (s *Service) ApplyLimit(ctx context.Context, input ApplyLimitInput) (Account, error) {
	if input.UserID == uuid.Nil {
		return Account{}, ErrAccountNotFound
	}
	if strings.TrimSpace(input.LimitCode) == "" {
		return Account{}, fmt.Errorf("%w: limit_code must be non-empty", ErrInvalidInput)
	}
	if err := input.Actor.Validate(); err != nil {
		return Account{}, err
	}
	if strings.TrimSpace(input.ReasonCode) == "" {
		return Account{}, fmt.Errorf("%w: reason_code must be non-empty", ErrInvalidInput)
	}

	now := s.deps.Now().UTC()
	expiresAt := input.ExpiresAt
	if expiresAt != nil {
		t := expiresAt.UTC()
		expiresAt = &t
	}

	if err := s.deps.Store.ApplyLimitTx(ctx, limitInsert{
		UserID:     input.UserID,
		LimitCode:  input.LimitCode,
		Value:      input.Value,
		ReasonCode: input.ReasonCode,
		Actor:      input.Actor,
		AppliedAt:  now,
		ExpiresAt:  expiresAt,
	}); err != nil {
		if errors.Is(err, ErrAccountNotFound) {
			return Account{}, err
		}
		return Account{}, fmt.Errorf("user apply limit: %w", err)
	}
	return s.GetAccount(ctx, input.UserID)
}
