package service

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"

	accessevents "github.com/codex-k8s/kodex/libs/go/platformevents/access"
	"github.com/codex-k8s/kodex/services/internal/access-manager/internal/domain/types/entity"
	"github.com/codex-k8s/kodex/services/internal/access-manager/internal/domain/types/value"
)

type accessPayloadStringField uint8

const (
	accessPayloadOrganizationID accessPayloadStringField = iota + 1
	accessPayloadKind
	accessPayloadStatus
	accessPayloadGroupID
	accessPayloadScopeType
	accessPayloadScopeID
	accessPayloadExternalAccountID
	accessPayloadExternalProviderID
	accessPayloadAccountType
	accessPayloadExternalAccountBindingID
	accessPayloadUsageScopeType
	accessPayloadUsageScopeID
)

type accessEventPayloadOption func(*value.AccessEventPayload)

func (s *Service) event(eventType string, aggregateType string, aggregateID uuid.UUID, payload value.AccessEventPayload, occurredAt time.Time) (entity.OutboxEvent, error) {
	eventID := s.ids.New()
	raw, err := json.Marshal(payload)
	if err != nil {
		return entity.OutboxEvent{}, fmt.Errorf("marshal access event payload %s: %w", eventType, err)
	}
	return entity.OutboxEvent{
		ID: eventID, EventType: eventType, SchemaVersion: accessevents.SchemaVersion,
		AggregateType: aggregateType, AggregateID: aggregateID, Payload: raw, OccurredAt: occurredAt,
	}, nil
}

func (s *Service) membershipEvent(eventType string, membership entity.Membership, occurredAt time.Time, reasonCode string) (entity.OutboxEvent, error) {
	return s.event(eventType, accessAggregateMembership, membership.ID, value.AccessEventPayload{
		MembershipID: membership.ID.String(),
		SubjectType:  string(membership.SubjectType),
		SubjectID:    membership.SubjectID.String(),
		TargetType:   string(membership.TargetType),
		TargetID:     membership.TargetID.String(),
		ReasonCode:   strings.TrimSpace(reasonCode),
		Version:      membership.Version,
	}, occurredAt)
}

func (s *Service) createdEvent(
	eventType string,
	aggregateType string,
	aggregateID uuid.UUID,
	occurredAt time.Time,
	options ...accessEventPayloadOption,
) (entity.OutboxEvent, error) {
	payload := value.AccessEventPayload{}
	for _, option := range options {
		option(&payload)
	}
	return s.event(eventType, aggregateType, aggregateID, payload, occurredAt)
}

func payloadString(field accessPayloadStringField, text string) accessEventPayloadOption {
	return func(payload *value.AccessEventPayload) {
		switch field {
		case accessPayloadOrganizationID:
			payload.OrganizationID = text
		case accessPayloadKind:
			payload.Kind = text
		case accessPayloadStatus:
			payload.Status = text
		case accessPayloadGroupID:
			payload.GroupID = text
		case accessPayloadScopeType:
			payload.ScopeType = text
		case accessPayloadScopeID:
			payload.ScopeID = text
		case accessPayloadExternalAccountID:
			payload.ExternalAccountID = text
		case accessPayloadExternalProviderID:
			payload.ExternalProviderID = text
		case accessPayloadAccountType:
			payload.AccountType = text
		case accessPayloadExternalAccountBindingID:
			payload.ExternalAccountBindingID = text
		case accessPayloadUsageScopeType:
			payload.UsageScopeType = text
		case accessPayloadUsageScopeID:
			payload.UsageScopeID = text
		}
	}
}

func payloadVersion(version int64) accessEventPayloadOption {
	return func(payload *value.AccessEventPayload) {
		payload.Version = version
	}
}
