package jitsi

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

// ========================================
// Test Helpers
// ========================================

func createTestService() *JitsiService {
	return NewJitsiService(Config{
		BaseURL:   "http://localhost:8443",
		AppID:     "breakpilot",
		AppSecret: "test-secret-key",
	})
}

func createTestServiceWithoutAuth() *JitsiService {
	return NewJitsiService(Config{
		BaseURL: "http://localhost:8443",
	})
}

// ========================================
// Unit Tests: Service Creation
// ========================================

func TestNewJitsiService_ValidConfig_CreatesService(t *testing.T) {
	cfg := Config{
		BaseURL:   "http://localhost:8443",
		AppID:     "test-app",
		AppSecret: "test-secret",
	}

	service := NewJitsiService(cfg)

	if service == nil {
		t.Fatal("Expected service to be created, got nil")
	}
	if service.baseURL != cfg.BaseURL {
		t.Errorf("Expected baseURL %s, got %s", cfg.BaseURL, service.baseURL)
	}
	if service.appID != cfg.AppID {
		t.Errorf("Expected appID %s, got %s", cfg.AppID, service.appID)
	}
	if service.appSecret != cfg.AppSecret {
		t.Errorf("Expected appSecret %s, got %s", cfg.AppSecret, service.appSecret)
	}
	if service.httpClient == nil {
		t.Error("Expected httpClient to be initialized")
	}
}

func TestNewJitsiService_TrailingSlash_Removed(t *testing.T) {
	service := NewJitsiService(Config{
		BaseURL: "http://localhost:8443/",
	})

	if service.baseURL != "http://localhost:8443" {
		t.Errorf("Expected trailing slash to be removed, got %s", service.baseURL)
	}
}

func TestGetBaseURL_ReturnsConfiguredURL(t *testing.T) {
	service := createTestService()

	result := service.GetBaseURL()

	if result != "http://localhost:8443" {
		t.Errorf("Expected 'http://localhost:8443', got '%s'", result)
	}
}

func TestIsAuthEnabled_WithSecret_ReturnsTrue(t *testing.T) {
	service := createTestService()

	if !service.IsAuthEnabled() {
		t.Error("Expected auth to be enabled when secret is configured")
	}
}

func TestIsAuthEnabled_WithoutSecret_ReturnsFalse(t *testing.T) {
	service := createTestServiceWithoutAuth()

	if service.IsAuthEnabled() {
		t.Error("Expected auth to be disabled when secret is not configured")
	}
}

// ========================================
// Unit Tests: Room Name Generation
// ========================================

func TestSanitizeRoomName_ValidInput_ReturnsCleanName(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected string
	}{
		{
			name:     "simple name",
			input:    "meeting",
			expected: "meeting",
		},
		{
			name:     "with spaces",
			input:    "My Meeting Room",
			expected: "my-meeting-room",
		},
		{
			name:     "german umlauts",
			input:    "Schüler Müller",
			expected: "schueler-mueller",
		},
		{
			name:     "special characters",
			input:    "Test@#$%Meeting!",
			expected: "testmeeting",
		},
		{
			name:     "consecutive hyphens",
			input:    "test---meeting",
			expected: "test-meeting",
		},
		{
			name:     "leading trailing hyphens",
			input:    "-test-meeting-",
			expected: "test-meeting",
		},
		{
			name:     "eszett",
			input:    "Straße",
			expected: "strasse",
		},
		{
			name:     "numbers",
			input:    "Klasse 5a",
			expected: "klasse-5a",
		},
	}

	service := createTestService()

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := service.sanitizeRoomName(tt.input)
			if result != tt.expected {
				t.Errorf("Expected '%s', got '%s'", tt.expected, result)
			}
		})
	}
}

func TestSanitizeRoomName_LongName_Truncated(t *testing.T) {
	service := createTestService()
	longName := strings.Repeat("a", 100)

	result := service.sanitizeRoomName(longName)

	if len(result) > 50 {
		t.Errorf("Expected max 50 chars, got %d", len(result))
	}
}

func TestGenerateRoomName_ReturnsUniqueNames(t *testing.T) {
	service := createTestService()

	name1 := service.generateRoomName()
	name2 := service.generateRoomName()

	if name1 == name2 {
		t.Error("Expected unique room names")
	}
	if !strings.HasPrefix(name1, "breakpilot-") {
		t.Errorf("Expected prefix 'breakpilot-', got '%s'", name1)
	}
}

func TestGenerateTrainingRoomName_IncludesTitle(t *testing.T) {
	service := createTestService()

	result := service.generateTrainingRoomName("Go Workshop")

	if !strings.HasPrefix(result, "go-workshop-") {
		t.Errorf("Expected to start with 'go-workshop-', got '%s'", result)
	}
}

func TestGeneratePassword_ReturnsValidPassword(t *testing.T) {
	service := createTestService()

	password := service.generatePassword()

	if len(password) != 8 {
		t.Errorf("Expected 8 char password, got %d", len(password))
	}
}

// ========================================
// Unit Tests: Meeting Link Creation
// ========================================

func TestCreateMeetingLink_BasicMeeting_ReturnsValidLink(t *testing.T) {
	service := createTestServiceWithoutAuth()

	meeting := Meeting{
		RoomName:    "test-room",
		DisplayName: "Test User",
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.RoomName != "test-room" {
		t.Errorf("Expected room name 'test-room', got '%s'", link.RoomName)
	}
	if link.URL != "http://localhost:8443/test-room" {
		t.Errorf("Expected URL 'http://localhost:8443/test-room', got '%s'", link.URL)
	}
	if link.JoinURL != "http://localhost:8443/test-room" {
		t.Errorf("Expected JoinURL 'http://localhost:8443/test-room', got '%s'", link.JoinURL)
	}
}

func TestCreateMeetingLink_NoRoomName_GeneratesName(t *testing.T) {
	service := createTestServiceWithoutAuth()

	meeting := Meeting{
		DisplayName: "Test User",
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.RoomName == "" {
		t.Error("Expected room name to be generated")
	}
	if !strings.HasPrefix(link.RoomName, "breakpilot-") {
		t.Errorf("Expected generated room name to start with 'breakpilot-', got '%s'", link.RoomName)
	}
}

func TestCreateMeetingLink_WithPassword_IncludesPassword(t *testing.T) {
	service := createTestServiceWithoutAuth()

	meeting := Meeting{
		RoomName: "test-room",
		Password: "secret123",
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.Password != "secret123" {
		t.Errorf("Expected password 'secret123', got '%s'", link.Password)
	}
}

func TestCreateMeetingLink_WithAuth_IncludesJWT(t *testing.T) {
	service := createTestService()

	meeting := Meeting{
		RoomName:    "test-room",
		DisplayName: "Test User",
		Email:       "test@example.com",
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.JWT == "" {
		t.Error("Expected JWT to be generated")
	}
	if !strings.Contains(link.JoinURL, "jwt=") {
		t.Error("Expected JoinURL to contain JWT parameter")
	}
	if link.ExpiresAt == nil {
		t.Error("Expected ExpiresAt to be set")
	}
}

func TestCreateMeetingLink_WithConfig_IncludesParams(t *testing.T) {
	service := createTestServiceWithoutAuth()

	meeting := Meeting{
		RoomName: "test-room",
		Config: &MeetingConfig{
			StartWithAudioMuted: true,
			StartWithVideoMuted: true,
			RequireDisplayName:  true,
		},
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if !strings.Contains(link.JoinURL, "startWithAudioMuted=true") {
		t.Error("Expected JoinURL to contain audio muted config")
	}
	if !strings.Contains(link.JoinURL, "startWithVideoMuted=true") {
		t.Error("Expected JoinURL to contain video muted config")
	}
}

func TestCreateMeetingLink_Moderator_SetsModeratorURL(t *testing.T) {
	service := createTestService()

	meeting := Meeting{
		RoomName:    "test-room",
		DisplayName: "Admin",
		Moderator:   true,
	}

	link, err := service.CreateMeetingLink(context.Background(), meeting)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.ModeratorURL == "" {
		t.Error("Expected ModeratorURL to be set for moderator")
	}
}

// ========================================
// Unit Tests: Specialized Meeting Types
// ========================================

func TestCreateTrainingSession_ReturnsOptimizedConfig(t *testing.T) {
	service := createTestServiceWithoutAuth()

	link, err := service.CreateTrainingSession(
		context.Background(),
		"Go Grundlagen",
		"Max Trainer",
		"trainer@example.com",
		60,
	)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if !strings.Contains(link.RoomName, "go-grundlagen") {
		t.Errorf("Expected room name to contain 'go-grundlagen', got '%s'", link.RoomName)
	}
	// Config should have lobby enabled for training
	if !strings.Contains(link.JoinURL, "enableLobby=true") {
		t.Error("Expected training to have lobby enabled")
	}
}

func TestCreateQuickMeeting_ReturnsSimpleMeeting(t *testing.T) {
	service := createTestServiceWithoutAuth()

	link, err := service.CreateQuickMeeting(context.Background(), "Quick User")

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if link.RoomName == "" {
		t.Error("Expected room name to be generated")
	}
}

func TestCreateParentTeacherMeeting_ReturnsSecureMeeting(t *testing.T) {
	service := createTestServiceWithoutAuth()
	scheduledTime := time.Now().Add(24 * time.Hour)

	link, err := service.CreateParentTeacherMeeting(
		context.Background(),
		"Frau Müller",
		"Herr Schmidt",
		"Max Mustermann",
		scheduledTime,
	)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if !strings.Contains(link.RoomName, "elterngespraech") {
		t.Errorf("Expected room name to contain 'elterngespraech', got '%s'", link.RoomName)
	}
	if link.Password == "" {
		t.Error("Expected password for parent-teacher meeting")
	}
	if !strings.Contains(link.JoinURL, "enableLobby=true") {
		t.Error("Expected lobby to be enabled")
	}
}

func TestCreateClassMeeting_ReturnsMeetingForClass(t *testing.T) {
	service := createTestServiceWithoutAuth()

	link, err := service.CreateClassMeeting(
		context.Background(),
		"5a",
		"Herr Lehrer",
		"Mathematik",
	)

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if !strings.Contains(link.RoomName, "klasse-5a") {
		t.Errorf("Expected room name to contain 'klasse-5a', got '%s'", link.RoomName)
	}
	// Students should be muted by default
	if !strings.Contains(link.JoinURL, "startWithAudioMuted=true") {
		t.Error("Expected students to start muted")
	}
}

// ========================================
// Unit Tests: JWT Generation
// ========================================

func TestGenerateJWT_ValidClaims_ReturnsValidToken(t *testing.T) {
	service := createTestService()

	meeting := Meeting{
		RoomName:    "test-room",
		DisplayName: "Test User",
		Email:       "test@example.com",
		Moderator:   true,
		Duration:    60,
	}

	token, expiresAt, err := service.generateJWT(meeting, "test-room")

	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}
	if token == "" {
		t.Error("Expected token to be generated")
	}
	if expiresAt == nil {
		t.Error("Expected expiration time to be set")
	}

	// Verify token structure (header.payload.signature)
	parts := strings.Split(token, ".")
	if len(parts) != 3 {
		t.Errorf("Expected 3 JWT parts, got %d", len(parts))
	}

	// Decode and verify payload
	payloadJSON, err := base64.RawURLEncoding.DecodeString(parts[1])
	if err != nil {
		t.Fatalf("Failed to decode payload: %v", err)
	}

	var claims JWTClaims
	if err := json.Unmarshal(payloadJSON, &claims); err != nil {
		t.Fatalf("Failed to unmarshal claims: %v", err)
	}

	if claims.Room != "test-room" {
		t.Errorf("Expected room 'test-room', got '%s'", claims.Room)
	}
	if !claims.Moderator {
		t.Error("Expected moderator to be true")
	}
	if claims.Context == nil || claims.Context.User == nil {
		t.Error("Expected user context to be set")
	}
	if claims.Context.User.Name != "Test User" {
		t.Errorf("Expected user name 'Test User', got '%s'", claims.Context.User.Name)
	}
}

func TestGenerateJWT_WithFeatures_IncludesFeatures(t *testing.T) {
	service := createTestService()

	meeting := Meeting{
		RoomName: "test-room",
		Features: &MeetingFeatures{
			Recording:     true,
			Transcription: true,
		},
	}

	token, _, err := service.generateJWT(meeting, "test-room")
	if err != nil {
		t.Fatalf("Expected no error, got %v", err)
	}

	parts := strings.Split(token, ".")
	payloadJSON, _ := base64.RawURLEncoding.DecodeString(parts[1])

	var claims JWTClaims
	json.Unmarshal(payloadJSON, &claims)

	if claims.Features == nil {
		t.Error("Expected features to be set")
	}
	if claims.Features.Recording != "true" {
		t.Errorf("Expected recording 'true', got '%s'", claims.Features.Recording)
	}
}

func TestGenerateJWT_NoSecret_ReturnsError(t *testing.T) {
	service := createTestServiceWithoutAuth()

	meeting := Meeting{RoomName: "test"}

	_, _, err := service.generateJWT(meeting, "test")

	if err == nil {
		t.Error("Expected error when secret is not configured")
	}
}

// ========================================
// Unit Tests: Health Check
// ========================================

func TestHealthCheck_ServerAvailable_ReturnsNil(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	service := NewJitsiService(Config{BaseURL: server.URL})

	err := service.HealthCheck(context.Background())

	if err != nil {
		t.Errorf("Expected no error, got %v", err)
	}
}

func TestHealthCheck_ServerError_ReturnsError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusInternalServerError)
	}))
	defer server.Close()

	service := NewJitsiService(Config{BaseURL: server.URL})

	err := service.HealthCheck(context.Background())

	if err == nil {
		t.Error("Expected error for server error response")
	}
}

func TestHealthCheck_ServerUnreachable_ReturnsError(t *testing.T) {
	service := NewJitsiService(Config{BaseURL: "http://localhost:59999"})

	ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
	defer cancel()

	err := service.HealthCheck(ctx)

	if err == nil {
		t.Error("Expected error for unreachable server")
	}
}

// ========================================
// Unit Tests: URL Building
// ========================================

func TestBuildEmbedURL_BasicRoom_ReturnsURL(t *testing.T) {
	service := createTestService()

	url := service.BuildEmbedURL("test-room", "", nil)

	if url != "http://localhost:8443/test-room" {
		t.Errorf("Expected 'http://localhost:8443/test-room', got '%s'", url)
	}
}

func TestBuildEmbedURL_WithDisplayName_IncludesParam(t *testing.T) {
	service := createTestService()

	url := service.BuildEmbedURL("test-room", "Max Mustermann", nil)

	if !strings.Contains(url, "displayName=Max") {
		t.Errorf("Expected URL to contain display name, got '%s'", url)
	}
}

func TestBuildEmbedURL_WithConfig_IncludesParams(t *testing.T) {
	service := createTestService()

	config := &MeetingConfig{
		StartWithAudioMuted: true,
		StartWithVideoMuted: true,
	}

	url := service.BuildEmbedURL("test-room", "", config)

	if !strings.Contains(url, "startWithAudioMuted=true") {
		t.Error("Expected URL to contain audio muted config")
	}
	if !strings.Contains(url, "startWithVideoMuted=true") {
		t.Error("Expected URL to contain video muted config")
	}
}

func TestBuildIFrameCode_DefaultSize_Returns800x600(t *testing.T) {
	service := createTestService()

	code := service.BuildIFrameCode("test-room", 0, 0)

	if !strings.Contains(code, "width=\"800\"") {
		t.Error("Expected default width 800")
	}
	if !strings.Contains(code, "height=\"600\"") {
		t.Error("Expected default height 600")
	}
	if !strings.Contains(code, "test-room") {
		t.Error("Expected room name in iframe")
	}
	if !strings.Contains(code, "allow=\"camera; microphone") {
		t.Error("Expected camera/microphone permissions")
	}
}

func TestBuildIFrameCode_CustomSize_ReturnsCorrectDimensions(t *testing.T) {
	service := createTestService()

	code := service.BuildIFrameCode("test-room", 1920, 1080)

	if !strings.Contains(code, "width=\"1920\"") {
		t.Error("Expected width 1920")
	}
	if !strings.Contains(code, "height=\"1080\"") {
		t.Error("Expected height 1080")
	}
}

// ========================================
// Unit Tests: Server Info
// ========================================

func TestGetServerInfo_ReturnsInfo(t *testing.T) {
	service := createTestService()

	info := service.GetServerInfo()

	if info["base_url"] != "http://localhost:8443" {
		t.Errorf("Expected base_url, got '%s'", info["base_url"])
	}
	if info["app_id"] != "breakpilot" {
		t.Errorf("Expected app_id 'breakpilot', got '%s'", info["app_id"])
	}
	if info["auth_enabled"] != "true" {
		t.Errorf("Expected auth_enabled 'true', got '%s'", info["auth_enabled"])
	}
}

// ========================================
// Unit Tests: Helper Functions
// ========================================

func TestBoolToString_True_ReturnsTrue(t *testing.T) {
	result := boolToString(true)
	if result != "true" {
		t.Errorf("Expected 'true', got '%s'", result)
	}
}

func TestBoolToString_False_ReturnsFalse(t *testing.T) {
	result := boolToString(false)
	if result != "false" {
		t.Errorf("Expected 'false', got '%s'", result)
	}
}
