package service

import (
	"context"
	"testing"

	"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/services/persona-api/internal/adapter/memory"
	"git.threesix.ai/jordan/persona-community-5/services/persona-api/internal/domain"
)

// mockProducer implements queue.Producer for testing.
type mockProducer struct {
	jobs []struct {
		jobType string
		payload map[string]any
	}
}

var _ queue.Producer = (*mockProducer)(nil)

func (m *mockProducer) Enqueue(_ context.Context, jobType string, payload map[string]any) (string, error) {
	m.jobs = append(m.jobs, struct {
		jobType string
		payload map[string]any
	}{jobType: jobType, payload: payload})
	return "test-job-id", nil
}

func (m *mockProducer) EnqueueWithOptions(_ context.Context, job queue.Job) (string, error) {
	m.jobs = append(m.jobs, struct {
		jobType string
		payload map[string]any
	}{jobType: job.Type, payload: job.Payload})
	return "test-job-id", nil
}

func TestPersonaService_Create(t *testing.T) {
	repo := memory.NewPersonaRepository()
	q := &mockProducer{}
	svc := NewPersonaService(repo, q, nil, logging.Nop())

	t.Run("creates persona with custom name", func(t *testing.T) {
		persona, err := svc.Create(context.Background(), "mysterious woman with dark hair", "woman", "Luna Shadow")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if persona.Name != "Luna Shadow" {
			t.Errorf("expected name 'Luna Shadow', got '%s'", persona.Name)
		}
		if persona.ID.IsZero() {
			t.Error("expected non-empty ID")
		}
		if persona.Gender != "woman" {
			t.Errorf("expected gender 'woman', got '%s'", persona.Gender)
		}
		if persona.Status != domain.PersonaStatusPending {
			t.Errorf("expected status 'pending', got '%s'", persona.Status)
		}
		if persona.Handle == "" {
			t.Error("expected non-empty handle")
		}
	})

	t.Run("creates persona with generated name", func(t *testing.T) {
		persona, err := svc.Create(context.Background(), "energetic man who loves music", "man", "")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if persona.Name == "" {
			t.Error("expected auto-generated name")
		}
		if persona.ID.IsZero() {
			t.Error("expected non-empty ID")
		}
	})

	t.Run("enqueues generate_spec job", func(t *testing.T) {
		found := false
		for _, j := range q.jobs {
			if j.jobType == "generate_spec" {
				found = true
				if j.payload["stage"] != "spec" {
					t.Errorf("expected stage 'spec', got %v", j.payload["stage"])
				}
				if _, ok := j.payload["persona_id"]; !ok {
					t.Error("expected persona_id in job payload")
				}
			}
		}
		if !found {
			t.Error("expected generate_spec job to be enqueued")
		}
	})

	t.Run("initializes empty slices", func(t *testing.T) {
		persona, err := svc.Create(context.Background(), "test persona for slices", "non_binary", "SliceTest")
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if persona.Tags == nil {
			t.Error("expected Tags to be initialized (not nil)")
		}
		if persona.ImageURLs == nil {
			t.Error("expected ImageURLs to be initialized (not nil)")
		}
		if persona.VideoURLs == nil {
			t.Error("expected VideoURLs to be initialized (not nil)")
		}
	})
}

func TestPersonaService_GetByID(t *testing.T) {
	repo := memory.NewPersonaRepository()
	q := &mockProducer{}
	svc := NewPersonaService(repo, q, nil, logging.Nop())

	created, _ := svc.Create(context.Background(), "test persona", "woman", "GetTest")

	t.Run("returns existing persona", func(t *testing.T) {
		persona, err := svc.GetByID(context.Background(), created.ID)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if persona.Name != "GetTest" {
			t.Errorf("expected name 'GetTest', got '%s'", persona.Name)
		}
	})

	t.Run("returns not found for missing persona", func(t *testing.T) {
		_, err := svc.GetByID(context.Background(), "nonexistent-id")
		if err != domain.ErrPersonaNotFound {
			t.Errorf("expected ErrPersonaNotFound, got %v", err)
		}
	})
}

func TestPersonaService_List(t *testing.T) {
	repo := memory.NewPersonaRepository()
	q := &mockProducer{}
	svc := NewPersonaService(repo, q, nil, logging.Nop())

	t.Run("returns empty list initially", func(t *testing.T) {
		personas, err := svc.List(context.Background(), 20, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 0 {
			t.Errorf("expected 0 personas, got %d", len(personas))
		}
	})

	// Create some personas
	_, _ = svc.Create(context.Background(), "persona one", "woman", "ListOne")
	_, _ = svc.Create(context.Background(), "persona two", "man", "ListTwo")
	_, _ = svc.Create(context.Background(), "persona three", "non_binary", "ListThree")

	t.Run("returns all personas", func(t *testing.T) {
		personas, err := svc.List(context.Background(), 20, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 3 {
			t.Errorf("expected 3 personas, got %d", len(personas))
		}
	})

	t.Run("respects limit", func(t *testing.T) {
		personas, err := svc.List(context.Background(), 2, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 2 {
			t.Errorf("expected 2 personas, got %d", len(personas))
		}
	})

	t.Run("clamps negative limit to default", func(t *testing.T) {
		personas, err := svc.List(context.Background(), -1, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 3 {
			t.Errorf("expected 3 personas (default limit), got %d", len(personas))
		}
	})

	t.Run("clamps limit above 100", func(t *testing.T) {
		personas, err := svc.List(context.Background(), 200, 0)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 3 {
			t.Errorf("expected 3 personas (all within max limit), got %d", len(personas))
		}
	})

	t.Run("respects offset", func(t *testing.T) {
		personas, err := svc.List(context.Background(), 20, 2)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if len(personas) != 1 {
			t.Errorf("expected 1 persona, got %d", len(personas))
		}
	})
}

func TestGenerateHandle(t *testing.T) {
	tests := []struct {
		name  string
		input string
	}{
		{name: "simple name", input: "Luna Shadow"},
		{name: "special chars", input: "DJ Beats!@#"},
		{name: "unicode", input: "Café Noir"},
		{name: "long name", input: "This Is A Very Long Name That Exceeds The Maximum Handle Length Allowed"},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			handle := generateHandle(tt.input)
			if handle == "" {
				t.Error("expected non-empty handle")
			}
			if len(handle) > 50 {
				t.Errorf("handle too long: %d chars", len(handle))
			}
		})
	}
}

func TestGeneratePersonaName(t *testing.T) {
	tests := []struct {
		name  string
		desc  string
		check func(string) bool
	}{
		{
			name:  "short description",
			desc:  "mysterious woman",
			check: func(s string) bool { return s != "" },
		},
		{
			name:  "long description uses first 3 words",
			desc:  "mysterious woman with dark hair who loves poetry",
			check: func(s string) bool { return s != "" && len(s) <= 50 },
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			name := generatePersonaName(tt.desc)
			if !tt.check(name) {
				t.Errorf("unexpected name for desc %q: %q", tt.desc, name)
			}
		})
	}
}
