package service

import (
	"context"
	"fmt"
	"regexp"
	"strings"
	"time"

	"git.threesix.ai/jordan/persona-community-5/pkg/logging"
	"git.threesix.ai/jordan/persona-community-5/pkg/queue"
	"git.threesix.ai/jordan/persona-community-5/pkg/realtime"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/port"
)

// PersonaService handles persona-related business logic.
type PersonaService struct {
	repo   port.PersonaRepository
	queue  queue.Producer
	sseHub *realtime.SSEHub
	logger *logging.Logger
}

// NewPersonaService creates a new persona service.
func NewPersonaService(repo port.PersonaRepository, q queue.Producer, hub *realtime.SSEHub, logger *logging.Logger) *PersonaService {
	return &PersonaService{
		repo:   repo,
		queue:  q,
		sseHub: hub,
		logger: logger.WithService("PersonaService"),
	}
}

// Create creates a new persona and enqueues a generate_spec job.
func (s *PersonaService) Create(ctx context.Context, description, gender, customName string) (*domain.Persona, error) {
	name := customName
	if name == "" {
		name = generatePersonaName(description)
	}

	handle := generateHandle(name)
	now := time.Now().UTC()

	persona := &domain.Persona{
		Name:        name,
		Handle:      handle,
		Gender:      gender,
		Description: description,
		Tags:        []string{},
		ImageURLs:   []string{},
		VideoURLs:   []string{},
		Status:      domain.PersonaStatusPending,
		CreatedAt:   now,
	}

	if err := s.repo.Create(ctx, persona); err != nil {
		return nil, fmt.Errorf("create persona: %w", err)
	}

	// Enqueue generate_spec job to kick off the pipeline.
	_, err := s.queue.Enqueue(ctx, "generate_spec", map[string]any{
		"persona_id": persona.ID.String(),
		"stage":      string(domain.StageSpec),
	})
	if err != nil {
		s.logger.Error("failed to enqueue generate_spec job", "error", err, "persona_id", persona.ID)
		// Persona is created but job failed to enqueue — not fatal.
		// Caller can retry or a reconciler can pick it up.
	}

	s.publishUpdate(persona)
	s.logger.Info("persona created", "id", persona.ID, "name", name, "handle", handle)
	return persona, nil
}

// GetByID returns a persona by ID.
func (s *PersonaService) GetByID(ctx context.Context, id domain.PersonaID) (*domain.Persona, error) {
	return s.repo.GetByID(ctx, id)
}

// List returns personas with pagination.
func (s *PersonaService) List(ctx context.Context, limit, offset int) ([]*domain.Persona, error) {
	if limit <= 0 {
		limit = 20
	}
	if limit > 100 {
		limit = 100
	}
	if offset < 0 {
		offset = 0
	}
	return s.repo.List(ctx, limit, offset)
}

// publishUpdate sends a persona_updated SSE event to channel:personas.
func (s *PersonaService) publishUpdate(persona *domain.Persona) {
	if s.sseHub == nil {
		return
	}
	s.sseHub.SendToChannel("channel:personas", &realtime.SSEEvent{
		Type:   "persona_updated",
		Result: persona,
	})
}

// handleRegex strips non-alphanumeric characters for handle generation.
var handleRegex = regexp.MustCompile(`[^a-z0-9]+`)

// generateHandle creates a URL-safe handle from a name.
func generateHandle(name string) string {
	h := strings.ToLower(strings.TrimSpace(name))
	h = handleRegex.ReplaceAllString(h, "_")
	h = strings.Trim(h, "_")
	if len(h) > 40 {
		h = h[:40]
		h = strings.TrimRight(h, "_")
	}
	// Append timestamp suffix to reduce collisions
	suffix := fmt.Sprintf("_%d", time.Now().UnixMilli()%100000)
	return h + suffix
}

// generatePersonaName derives a placeholder name from the description.
func generatePersonaName(description string) string {
	words := strings.Fields(description)
	if len(words) > 3 {
		words = words[:3]
	}
	// Capitalize first letter of each word
	for i, w := range words {
		if len(w) > 0 {
			words[i] = strings.ToUpper(w[:1]) + w[1:]
		}
	}
	name := strings.Join(words, " ")
	if len(name) > 50 {
		name = name[:50]
	}
	return name
}
