/**
 * Tests for the analyze-task-complexity.js module
 */
import { jest } from '@jest/globals';
import {
	createGetTagAwareFilePathMock,
	createSlugifyTagForFilePathMock
} from './setup.js';

// Mock the dependencies before importing the module under test
jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({
	readJSON: jest.fn(),
	writeJSON: jest.fn(),
	log: jest.fn(),
	CONFIG: {
		model: 'mock-claude-model',
		maxTokens: 4000,
		temperature: 0.7,
		debug: false,
		defaultSubtasks: 3
	},
	findTaskById: jest.fn(),
	readComplexityReport: jest.fn(),
	findTaskInComplexityReport: jest.fn(),
	findProjectRoot: jest.fn(() => '/mock/project/root'),
	resolveEnvVariable: jest.fn((varName) => `mock_${varName}`),
	isSilentMode: jest.fn(() => false),
	findCycles: jest.fn(() => []),
	formatTaskId: jest.fn((id) => `Task ${id}`),
	taskExists: jest.fn((tasks, id) => tasks.some((t) => t.id === id)),
	enableSilentMode: jest.fn(),
	disableSilentMode: jest.fn(),
	truncate: jest.fn((text) => text),
	addComplexityToTask: jest.fn((task, complexity) => ({ ...task, complexity })),
	aggregateTelemetry: jest.fn((telemetryArray) => telemetryArray[0] || {}),
	ensureTagMetadata: jest.fn((tagObj) => tagObj),
	getCurrentTag: jest.fn(() => 'master'),
	flattenTasksWithSubtasks: jest.fn((tasks) => tasks),
	getTagAwareFilePath: createGetTagAwareFilePathMock(),
	slugifyTagForFilePath: createSlugifyTagForFilePathMock(),
	markMigrationForNotice: jest.fn(),
	performCompleteTagMigration: jest.fn(),
	setTasksForTag: jest.fn(),
	getTasksForTag: jest.fn((data, tag) => data[tag]?.tasks || []),
	traverseDependencies: jest.fn((tasks, taskId, visited) => [])
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/ai-services-unified.js',
	() => ({
		generateObjectService: jest.fn().mockResolvedValue({
			mainResult: {
				complexityAnalysis: []
			},
			telemetryData: {
				timestamp: new Date().toISOString(),
				userId: '1234567890',
				commandName: 'analyze-complexity',
				modelUsed: 'claude-3-5-sonnet',
				providerName: 'anthropic',
				inputTokens: 1000,
				outputTokens: 500,
				totalTokens: 1500,
				totalCost: 0.012414,
				currency: 'USD'
			}
		}),
		generateTextService: jest.fn().mockResolvedValue({
			mainResult: '[]',
			telemetryData: {
				timestamp: new Date().toISOString(),
				userId: '1234567890',
				commandName: 'analyze-complexity',
				modelUsed: 'claude-3-5-sonnet',
				providerName: 'anthropic',
				inputTokens: 1000,
				outputTokens: 500,
				totalTokens: 1500,
				totalCost: 0.012414,
				currency: 'USD'
			}
		}),
		streamTextService: jest.fn().mockResolvedValue({
			mainResult: async function* () {
				yield '{"tasks":[';
				yield '{"id":1,"title":"Test Task","priority":"high"}';
				yield ']}';
			},
			telemetryData: {
				timestamp: new Date().toISOString(),
				userId: '1234567890',
				commandName: 'analyze-complexity',
				modelUsed: 'claude-3-5-sonnet',
				providerName: 'anthropic',
				inputTokens: 1000,
				outputTokens: 500,
				totalTokens: 1500,
				totalCost: 0.012414,
				currency: 'USD'
			}
		}),
		streamObjectService: jest.fn().mockImplementation(async () => {
			return {
				get partialObjectStream() {
					return (async function* () {
						yield { tasks: [] };
						yield { tasks: [{ id: 1, title: 'Test Task', priority: 'high' }] };
					})();
				},
				object: Promise.resolve({
					tasks: [{ id: 1, title: 'Test Task', priority: 'high' }]
				})
			};
		})
	})
);

jest.unstable_mockModule(
	'../../../../../scripts/modules/config-manager.js',
	() => ({
		// Core config access
		getConfig: jest.fn(() => ({
			models: { main: { provider: 'anthropic', modelId: 'claude-3-5-sonnet' } },
			global: { projectName: 'Test Project' }
		})),
		writeConfig: jest.fn(() => true),
		ConfigurationError: class extends Error {},
		isConfigFilePresent: jest.fn(() => true),

		// Validation
		validateProvider: jest.fn(() => true),
		validateProviderModelCombination: jest.fn(() => true),
		VALID_PROVIDERS: ['anthropic', 'openai', 'perplexity'],
		MODEL_MAP: {
			anthropic: [
				{
					id: 'claude-3-5-sonnet',
					cost_per_1m_tokens: { input: 3, output: 15 }
				}
			],
			openai: [{ id: 'gpt-4', cost_per_1m_tokens: { input: 30, output: 60 } }]
		},
		getAvailableModels: jest.fn(() => [
			{
				id: 'claude-3-5-sonnet',
				name: 'Claude 3.5 Sonnet',
				provider: 'anthropic'
			},
			{ id: 'gpt-4', name: 'GPT-4', provider: 'openai' }
		]),

		// Role-specific getters
		getMainProvider: jest.fn(() => 'anthropic'),
		getMainModelId: jest.fn(() => 'claude-3-5-sonnet'),
		getMainMaxTokens: jest.fn(() => 4000),
		getMainTemperature: jest.fn(() => 0.7),
		getResearchProvider: jest.fn(() => 'perplexity'),
		getResearchModelId: jest.fn(() => 'sonar-pro'),
		getResearchMaxTokens: jest.fn(() => 8700),
		getResearchTemperature: jest.fn(() => 0.1),
		getFallbackProvider: jest.fn(() => 'anthropic'),
		getFallbackModelId: jest.fn(() => 'claude-3-5-sonnet'),
		getFallbackMaxTokens: jest.fn(() => 4000),
		getFallbackTemperature: jest.fn(() => 0.7),
		getBaseUrlForRole: jest.fn(() => undefined),

		// Global setting getters
		getLogLevel: jest.fn(() => 'info'),
		getDebugFlag: jest.fn(() => false),
		getDefaultNumTasks: jest.fn(() => 10),
		getDefaultSubtasks: jest.fn(() => 5),
		getDefaultPriority: jest.fn(() => 'medium'),
		getProjectName: jest.fn(() => 'Test Project'),
		getOllamaBaseURL: jest.fn(() => 'http://localhost:11434/api'),
		getAzureBaseURL: jest.fn(() => undefined),
		getBedrockBaseURL: jest.fn(() => undefined),
		getParametersForRole: jest.fn(() => ({
			maxTokens: 4000,
			temperature: 0.7
		})),
		getUserId: jest.fn(() => '1234567890'),

		// API Key Checkers
		isApiKeySet: jest.fn(() => true),
		getMcpApiKeyStatus: jest.fn(() => true),

		// Additional functions
		getAllProviders: jest.fn(() => ['anthropic', 'openai', 'perplexity']),
		getVertexProjectId: jest.fn(() => undefined),
		getVertexLocation: jest.fn(() => undefined),
		hasCodebaseAnalysis: jest.fn(() => false)
	})
);

// Mock fs module
const mockWriteFileSync = jest.fn();
jest.unstable_mockModule('fs', () => ({
	default: {
		existsSync: jest.fn(() => false),
		readFileSync: jest.fn(),
		writeFileSync: mockWriteFileSync,
		unlinkSync: jest.fn()
	},
	existsSync: jest.fn(() => false),
	readFileSync: jest.fn(),
	writeFileSync: mockWriteFileSync,
	unlinkSync: jest.fn()
}));

jest.unstable_mockModule(
	'../../../../../scripts/modules/prompt-manager.js',
	() => ({
		getPromptManager: jest.fn().mockReturnValue({
			loadPrompt: jest.fn().mockResolvedValue({
				systemPrompt: 'Mocked system prompt',
				userPrompt: 'Mocked user prompt'
			})
		})
	})
);

// Import the mocked modules
const { readJSON, writeJSON, log, CONFIG, findTaskById } = await import(
	'../../../../../scripts/modules/utils.js'
);

const { generateObjectService, generateTextService, streamTextService } =
	await import('../../../../../scripts/modules/ai-services-unified.js');

const fs = await import('fs');

// Import the module under test
const { default: analyzeTaskComplexity } = await import(
	'../../../../../scripts/modules/task-manager/analyze-task-complexity.js'
);

describe('analyzeTaskComplexity', () => {
	// Sample response structure (simplified for these tests)
	const sampleApiResponse = {
		mainResult: JSON.stringify({
			tasks: [
				{ id: 1, complexity: 3, subtaskCount: 2 },
				{ id: 2, complexity: 7, subtaskCount: 5 },
				{ id: 3, complexity: 9, subtaskCount: 8 }
			]
		}),
		telemetryData: {
			timestamp: new Date().toISOString(),
			userId: '1234567890',
			commandName: 'analyze-complexity',
			modelUsed: 'claude-3-5-sonnet',
			providerName: 'anthropic',
			inputTokens: 1000,
			outputTokens: 500,
			totalTokens: 1500,
			totalCost: 0.012414,
			currency: 'USD'
		}
	};

	const sampleTasks = {
		master: {
			tasks: [
				{
					id: 1,
					title: 'Task 1',
					description: 'First task description',
					status: 'pending',
					dependencies: [],
					priority: 'high'
				},
				{
					id: 2,
					title: 'Task 2',
					description: 'Second task description',
					status: 'pending',
					dependencies: [1],
					priority: 'medium'
				},
				{
					id: 3,
					title: 'Task 3',
					description: 'Third task description',
					status: 'done',
					dependencies: [1, 2],
					priority: 'high'
				}
			]
		}
	};

	beforeEach(() => {
		jest.clearAllMocks();

		// Default mock implementations - readJSON should return the resolved view with tasks at top level
		readJSON.mockImplementation((tasksPath, projectRoot, tag) => {
			return {
				...sampleTasks.master,
				tag: tag || 'master',
				_rawTaggedData: sampleTasks
			};
		});

		// Mock findTaskById to return the expected structure
		findTaskById.mockImplementation((tasks, taskId) => {
			const task = tasks?.find((t) => t.id === parseInt(taskId));
			return { task: task || null, originalSubtaskCount: null };
		});

		generateObjectService.mockResolvedValue({
			mainResult: {
				complexityAnalysis: JSON.parse(sampleApiResponse.mainResult).tasks
			},
			telemetryData: sampleApiResponse.telemetryData
		});
	});

	test('should call generateObjectService with the correct parameters', async () => {
		// Arrange
		const options = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: '5',
			research: false,
			projectRoot: '/mock/project/root'
		};

		// Act
		await analyzeTaskComplexity(options, {
			projectRoot: '/mock/project/root',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Assert
		expect(readJSON).toHaveBeenCalledWith(
			'tasks/tasks.json',
			'/mock/project/root',
			undefined
		);
		expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
		expect(mockWriteFileSync).toHaveBeenCalledWith(
			expect.stringContaining('task-complexity-report.json'),
			expect.stringContaining('"thresholdScore": 5'),
			'utf8'
		);
	});

	test('should use research flag to determine which AI service to use', async () => {
		// Arrange
		const researchOptions = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: '5',
			research: true,
			projectRoot: '/mock/project/root'
		};

		// Act
		await analyzeTaskComplexity(researchOptions, {
			projectRoot: '/mock/project/root',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Assert
		expect(generateObjectService).toHaveBeenCalledWith(
			expect.objectContaining({
				role: 'research' // This should be present when research is true
			})
		);
	});

	test('should handle different threshold parameter types correctly', async () => {
		// Test with string threshold
		let options = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: '7',
			projectRoot: '/mock/project/root'
		};

		await analyzeTaskComplexity(options, {
			projectRoot: '/mock/project/root',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		expect(mockWriteFileSync).toHaveBeenCalledWith(
			expect.stringContaining('task-complexity-report.json'),
			expect.stringContaining('"thresholdScore": 7'),
			'utf8'
		);

		// Reset mocks
		jest.clearAllMocks();

		// Test with number threshold
		options = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: 8,
			projectRoot: '/mock/project/root'
		};

		await analyzeTaskComplexity(options, {
			projectRoot: '/mock/project/root',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		expect(mockWriteFileSync).toHaveBeenCalledWith(
			expect.stringContaining('task-complexity-report.json'),
			expect.stringContaining('"thresholdScore": 8'),
			'utf8'
		);
	});

	test('should filter out completed tasks from analysis', async () => {
		// Arrange
		const options = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: '5',
			projectRoot: '/mock/project/root'
		};

		// Act
		await analyzeTaskComplexity(options, {
			projectRoot: '/mock/project/root',
			mcpLog: {
				info: jest.fn(),
				warn: jest.fn(),
				error: jest.fn(),
				debug: jest.fn(),
				success: jest.fn()
			}
		});

		// Assert
		// Check if the prompt sent to AI doesn't include the completed task (id: 3)
		expect(generateObjectService).toHaveBeenCalledWith(
			expect.objectContaining({
				prompt: expect.not.stringContaining('"id": 3')
			})
		);
	});

	test('should handle API errors gracefully', async () => {
		// Arrange
		const options = {
			file: 'tasks/tasks.json',
			output: 'scripts/task-complexity-report.json',
			threshold: '5',
			projectRoot: '/mock/project/root'
		};

		// Force API error
		generateObjectService.mockRejectedValueOnce(new Error('API Error'));

		const mockMcpLog = {
			info: jest.fn(),
			warn: jest.fn(),
			error: jest.fn(),
			debug: jest.fn(),
			success: jest.fn()
		};

		// Act & Assert
		await expect(
			analyzeTaskComplexity(options, {
				projectRoot: '/mock/project/root',
				mcpLog: mockMcpLog
			})
		).rejects.toThrow('API Error');

		// Check that the error was logged via mcpLog
		expect(mockMcpLog.error).toHaveBeenCalledWith(
			expect.stringContaining('API Error')
		);
	});
});
