/**
 * MCP Server Handler
 * 
 * Handles Model Context Protocol (MCP) requests with authentication context
 * Provides task management tools through Server-Sent Events (SSE) connection
 */

import { Env } from './index';
import { AuthContext } from './types';
import { TrackexAPIClient } from './trackex-api-client';
import { 
	MCPRequest, 
	MCPResponse, 
	MCPError, 
	MCPNotification,
	MCPTool, 
	MCPToolCall, 
	MCPToolResult,
	MCPSession,
	MCPServerConfig,
	MCPErrorCode,
	SSEMessage,
	TrackexTask
} from './types';

export class MCPServerHandler {
	private env: Env;
	private trackexClient: TrackexAPIClient;
	private tools: MCPTool[];

	constructor(env: Env) {
		this.env = env;
		this.trackexClient = new TrackexAPIClient(env);
		this.tools = this.initializeTools();
	}

	/**
	 * Initialize MCP tools
	 */
	private initializeTools(): MCPTool[] {
		return [
			{
				name: "get_today_tasks",
				description: "Get all tasks scheduled for today",
				inputSchema: {
					type: "object",
					properties: {},
					required: []
				}
			},
			{
				name: "get_today_adhoc_tasks", 
				description: "Get ad-hoc tasks for today (unscheduled urgent items)",
				inputSchema: {
					type: "object",
					properties: {},
					required: []
				}
			},
			{
				name: "get_today_pending_tasks",
				description: "Get pending tasks for today (waiting for dependencies)",
				inputSchema: {
					type: "object", 
					properties: {},
					required: []
				}
			},
			{
				name: "create_task",
				description: "Create a new task in Trackex",
				inputSchema: {
					type: "object",
					properties: {
						title: { type: "string", description: "Task title" },
						description: { type: "string", description: "Task description" },
						priority: { type: "string", enum: ["low", "medium", "high"], description: "Task priority" },
						due_date: { type: "string", description: "Due date in YYYY-MM-DD format" }
					},
					required: ["title"]
				}
			}
		];
	}

	/**
	 * Handle MCP requests with authentication context
	 */
	async handleRequest(request: Request, authContext: AuthContext, corsHeaders: Record<string, string>): Promise<Response> {
		const url = new URL(request.url);
		
		// For MCP, we primarily handle JSON-RPC over HTTP
		// SSE is used for transport but the protocol is still JSON-RPC
		
		// Handle JSON-RPC requests (the main MCP protocol)
		if (request.method === 'POST') {
			return this.handleJSONRPCRequest(request, authContext, corsHeaders);
		}

		// Handle SSE connection requests
		if (request.headers.get('Accept')?.includes('text/event-stream') || 
		    request.headers.get('Accept') === 'text/event-stream') {
			return this.handleSSEConnection(request, authContext, corsHeaders);
		}

		// Handle GET requests for capabilities discovery
		if (request.method === 'GET') {
			return this.handleCapabilitiesRequest(corsHeaders);
		}

		return new Response(JSON.stringify({
			error: 'Method Not Allowed',
			message: 'Only POST and GET methods are supported'
		}), {
			status: 405,
			headers: {
				'Content-Type': 'application/json',
				...corsHeaders,
			},
		});
	}

	/**
	 * Handle SSE connection for MCP JSON-RPC communication
	 */
	private async handleSSEConnection(request: Request, authContext: AuthContext, corsHeaders: Record<string, string>): Promise<Response> {
		console.log('🔌 SSE connection requested, method:', request.method);
		
		// For MCP over SSE, we need to handle the request body as JSON-RPC
		// SSE is used as the transport, but the protocol is still JSON-RPC
		
		if (request.method === 'POST') {
			// Handle JSON-RPC request over SSE transport
			return this.handleJSONRPCRequest(request, authContext, corsHeaders);
		}

		// For Claude.ai Remote MCP Server, GET requests should return capabilities, not establish SSE stream
		// Claude.ai doesn't need persistent SSE connections for remote MCP servers
		console.log('📡 GET request to SSE endpoint - returning capabilities instead of stream');
		
		return this.handleCapabilitiesRequest(corsHeaders);
	}

	/**
	 * Handle JSON-RPC requests
	 */
	private async handleJSONRPCRequest(request: Request, authContext: AuthContext, corsHeaders: Record<string, string>): Promise<Response> {
		let requestId: string | number | null = null;
		
		try {
			const body = await request.text();
			console.log('📥 Received MCP request:', body);
			
			let mcpRequest: MCPRequest;

			try {
				mcpRequest = JSON.parse(body) as MCPRequest;
				requestId = mcpRequest.id !== undefined ? mcpRequest.id : null;
				console.log('🔍 Parsed MCP request:', mcpRequest.method, 'ID:', requestId);
			} catch (parseError) {
				console.error('❌ JSON parse error:', parseError);
				return this.createErrorResponse(null, MCPErrorCode.ParseError, 'Invalid JSON', corsHeaders);
			}

			// Validate JSON-RPC format
			if (mcpRequest.jsonrpc !== '2.0' || !mcpRequest.method) {
				console.error('❌ Invalid JSON-RPC format:', mcpRequest);
				return this.createErrorResponse(
					requestId, 
					MCPErrorCode.InvalidRequest, 
					'Invalid JSON-RPC request', 
					corsHeaders
				);
			}

			// Handle different MCP methods
			const response = await this.processMethod(mcpRequest, authContext);
			console.log('📤 Sending MCP response:', JSON.stringify(response));
			
			// Ensure response is valid JSON-RPC 2.0
			if (!response.jsonrpc || response.jsonrpc !== '2.0') {
				console.error('❌ Invalid response format:', response);
				return this.createErrorResponse(
					requestId,
					MCPErrorCode.InternalError,
					'Invalid response format',
					corsHeaders
				);
			}
			
			// Wrap JSON serialization in try-catch
			try {
				const responseBody = JSON.stringify(response);
				return new Response(responseBody, {
					status: 200,
					headers: {
						'Content-Type': 'application/json',
						...corsHeaders,
					},
				});
			} catch (serializeError) {
				console.error('❌ JSON serialization error:', serializeError);
				return this.createErrorResponse(
					requestId,
					MCPErrorCode.InternalError,
					'JSON serialization failed',
					corsHeaders
				);
			}

		} catch (error) {
			console.error('❌ JSON-RPC request error:', error);
			return this.createErrorResponse(
				requestId, 
				MCPErrorCode.InternalError, 
				`Internal server error: ${error instanceof Error ? error.message : 'Unknown error'}`, 
				corsHeaders
			);
		}
	}

	/**
	 * Handle capabilities discovery request
	 */
	private handleCapabilitiesRequest(corsHeaders: Record<string, string>): Response {
		// Use a default MCP server URL - this should be set via environment variable
		const mcpServerUrl = this.env.MCP_SERVER_URL || 'https://trackex-mcp-server.lexystech.workers.dev';
		
		console.log('📋 Capabilities request - returning', this.tools.length, 'tools');
		console.log('🔍 GET capabilities - tool names:', this.tools.map(t => t.name).join(', '));
		console.log('🚨 CRITICAL: Including tools directly in capabilities since Claude.ai ignores tools/list');
		
		const mcpConfig = {
			protocol_version: "2024-11-05",
			server_info: {
				name: this.env.MCP_SERVER_NAME || "Trackex MCP Server",
				version: this.env.MCP_SERVER_VERSION || "1.0.0",
				description: "MCP Server for Trackex Task Management Integration with Microsoft Authentication"
			},
			capabilities: {
				tools: {
					listChanged: false
				},
				logging: {},
				prompts: {
					listChanged: false
				},
				resources: {
					subscribe: false,
					listChanged: false
				}
			},
			// FORCE TOOLS DIRECTLY - multiple formats to ensure Claude.ai sees them
			tools: this.tools,
			availableTools: this.tools,
			toolDefinitions: this.tools,
			// Also include tools list endpoint
			endpoints: {
				tools: `${mcpServerUrl}/tools`,
				toolsList: `${mcpServerUrl}/mcp`
			},
			// Include in transport section too
			transport: {
				type: "sse",
				endpoint: `${mcpServerUrl}/mcp`,
				tools: this.tools
			},
			auth: {
				type: "oauth2",
				authorization_endpoint: `${mcpServerUrl}/authorize`,
				token_endpoint: `${mcpServerUrl}/oauth/token`,
				scopes: ['mcp:read', 'mcp:write']
			}
		};

		console.log('📋 Capabilities response includes tools:', mcpConfig.tools.map(t => t.name).join(', '));

		return new Response(JSON.stringify(mcpConfig), {
			status: 200,
			headers: {
				'Content-Type': 'application/json',
				...corsHeaders,
			},
		});
	}

	/**
	 * Process MCP method calls
	 */
	private async processMethod(request: MCPRequest, authContext: AuthContext): Promise<MCPResponse> {
		// FORCE tools/list call if Claude.ai missed it
		if (request.method === 'initialize') {
			console.log('🚨 REMINDER: After initialize, Claude.ai MUST call tools/list to see tools');
		}

		// Log ALL incoming methods to debug what Claude.ai is calling
		console.log('📥 MCP Method Called:', request.method);
		console.log('🔄 Processing method:', request.method, 'Auth:', authContext.isAuthenticated);
		
		try {
			switch (request.method) {
				case 'initialize':
					// Initialize should work without authentication
					return this.handleInitialize(request);
				
				case 'initialized':
				case 'notifications/initialized':
					// Initialized should work without authentication
					return this.handleInitialized(request);
				
				case 'tools/list':
					// CRITICAL FIX: Tools list must be available WITHOUT authentication
					console.log('🎉 SUCCESS! Claude.ai is calling tools/list - this will show the tools!');
					console.log('🔧 Handling tools/list request - no auth required');
					return this.handleToolsList(request);
				
				case 'tools/call':
					// Tool calls definitely require authentication
					if (!authContext.isAuthenticated || !authContext.trackexToken) {
						console.log('❌ Tool call requires authentication');
											return this.createMCPErrorResponse(
						request.id !== undefined ? request.id : null,
						MCPErrorCode.AuthenticationError,
						'Authentication required for tool calls'
					);
					}
					return await this.handleToolCall(request, authContext.trackexToken);
				
				default:
					console.log('❌ Method not found:', request.method);
					return this.createMCPErrorResponse(
						request.id !== undefined ? request.id : null,
						MCPErrorCode.MethodNotFound,
						`Method '${request.method}' not found`
					);
			}
		} catch (error) {
			console.error('❌ Error processing method:', request.method, error);
			return this.createMCPErrorResponse(
				request.id !== undefined ? request.id : null,
				MCPErrorCode.InternalError,
				`Error processing method '${request.method}': ${error instanceof Error ? error.message : 'Unknown error'}`
			);
		}
	}

	/**
	 * Handle MCP initialize
	 */
	private handleInitialize(request: MCPRequest): MCPResponse {
		console.log('🚀 Handling initialize request, ID:', request.id);
		console.log('🔧 FORCING tools into initialize response since Claude.ai ignores tools/list');
		
		const response = {
			jsonrpc: '2.0' as const,
			id: request.id !== undefined ? request.id : null,
			result: {
				protocolVersion: '2024-11-05',
				capabilities: {
					tools: {
						listChanged: false
					},
					logging: {}
				},
				serverInfo: {
					name: this.env.MCP_SERVER_NAME || 'Trackex MCP Server',
					version: this.env.MCP_SERVER_VERSION || '1.0.0'
				},
				// FORCE TOOLS DIRECTLY INTO INITIALIZE RESPONSE
				tools: this.tools,
				// Alternative format that Claude.ai might recognize
				availableTools: this.tools.map(tool => ({
					name: tool.name,
					description: tool.description,
					schema: tool.inputSchema
				}))
			}
		};
		
		console.log('✅ Initialize response with FORCED TOOLS:', JSON.stringify(response));
		console.log('🎯 Tools included in response:', this.tools.map(t => t.name).join(', '));
		return response;
	}

	/**
	 * Handle tools list request
	 */
	private handleToolsList(request: MCPRequest): MCPResponse {
		console.log('🔧 Handling tools/list request, ID:', request.id);
		console.log('📋 Available tools:', this.tools.map(t => t.name));
		
		const response = {
			jsonrpc: '2.0' as const,
			id: request.id !== undefined ? request.id : null,
			result: {
				tools: this.tools
			}
		};
		
		console.log('✅ Tools list response prepared:');
		console.log('   - Tools count:', this.tools.length);
		console.log('   - Tool names:', this.tools.map(t => t.name).join(', '));
		console.log('   - Response ID:', response.id);
		return response;
	}

	/**
	 * Handle direct tools endpoint for debugging
	 */
	public handleDirectToolsRequest(corsHeaders: Record<string, string>): Response {
		console.log('🛠️ Direct /tools endpoint accessed');
		console.log('📋 Available tools:', this.tools.map(t => t.name));
		
		return new Response(JSON.stringify({
			protocol_version: "2024-11-05",
			tools: this.tools,
			debug: {
				tool_count: this.tools.length,
				tool_names: this.tools.map(t => t.name),
				server_name: this.env.MCP_SERVER_NAME || "Trackex MCP Server"
			}
		}), {
			status: 200,
			headers: {
				'Content-Type': 'application/json',
				...corsHeaders,
			},
		});
	}

	/**
	 * Handle tool call
	 */
	private async handleToolCall(request: MCPRequest, trackexToken: string): Promise<MCPResponse> {
		try {
			const { name, arguments: args } = request.params as MCPToolCall;
			
			// Validate session
			const isValidSession = await this.trackexClient.validateSession(trackexToken);
			if (!isValidSession) {
				return this.createMCPErrorResponse(
					request.id || null,
					MCPErrorCode.AuthenticationError,
					'Invalid or expired session'
				);
			}

			let result: MCPToolResult;

			switch (name) {
				case 'get_today_tasks':
					result = await this.executeGetTodayTasks(trackexToken);
					break;
				
				case 'get_today_adhoc_tasks':
					result = await this.executeGetTodayAdhocTasks(trackexToken);
					break;
				
				case 'get_today_pending_tasks':
					result = await this.executeGetTodayPendingTasks(trackexToken);
					break;
				
				case 'create_task':
					result = await this.executeCreateTask(trackexToken, args);
					break;
				
				default:
					return this.createMCPErrorResponse(
						request.id || null,
						MCPErrorCode.MethodNotFound,
						`Tool '${name}' not found`
					);
			}

			const response = {
				jsonrpc: '2.0' as const,
				id: request.id !== undefined ? request.id : null,
				result: result
			};
			
			console.log('✅ Tool call completed:', name);
			return response;

		} catch (error) {
			console.error('Tool call error:', error);
			return this.createMCPErrorResponse(
				request.id || null,
				MCPErrorCode.InternalError,
				`Tool execution failed: ${error instanceof Error ? error.message : 'Unknown error'}`
			);
		}
	}

	/**
	 * Handle initialized notification
	 */
	private handleInitialized(request: MCPRequest): MCPResponse {
		console.log('📢 Handling initialized notification, method:', request.method, 'ID:', request.id);
		
		// For notifications (like notifications/initialized), according to JSON-RPC spec,
		// notifications don't expect a response. But since we're over HTTP, we still return a response.
		const response = {
			jsonrpc: '2.0' as const,
			id: request.id !== undefined ? request.id : null,
			result: {}
		};
		
		console.log('✅ Initialized notification handled successfully');
		return response;
	}

	/**
	 * Execute get today tasks tool
	 */
	private async executeGetTodayTasks(trackexToken: string): Promise<MCPToolResult> {
		try {
			const tasks = await this.trackexClient.getTodayTasks(trackexToken);
			return {
				content: [{
					type: 'text',
					text: this.formatTasksResponse(tasks, "Today's Tasks")
				}]
			};
		} catch (error) {
			return {
				content: [{
					type: 'text',
					text: `Error fetching today's tasks: ${error instanceof Error ? error.message : 'Unknown error'}`
				}],
				isError: true
			};
		}
	}

	/**
	 * Execute get today ad-hoc tasks tool
	 */
	private async executeGetTodayAdhocTasks(trackexToken: string): Promise<MCPToolResult> {
		try {
			const tasks = await this.trackexClient.getTodayAdhocTasks(trackexToken);
			return {
				content: [{
					type: 'text',
					text: this.formatTasksResponse(tasks, "Today's Ad-hoc Tasks")
				}]
			};
		} catch (error) {
			return {
				content: [{
					type: 'text',
					text: `Error fetching today's ad-hoc tasks: ${error instanceof Error ? error.message : 'Unknown error'}`
				}],
				isError: true
			};
		}
	}

	/**
	 * Execute get today pending tasks tool
	 */
	private async executeGetTodayPendingTasks(trackexToken: string): Promise<MCPToolResult> {
		try {
			const tasks = await this.trackexClient.getTodayPendingTasks(trackexToken);
			return {
				content: [{
					type: 'text',
					text: this.formatTasksResponse(tasks, "Today's Pending Tasks")
				}]
			};
		} catch (error) {
			return {
				content: [{
					type: 'text',
					text: `Error fetching today's pending tasks: ${error instanceof Error ? error.message : 'Unknown error'}`
				}],
				isError: true
			};
		}
	}

	/**
	 * Execute create task tool
	 */
	private async executeCreateTask(trackexToken: string, args: any): Promise<MCPToolResult> {
		try {
			// Validate required parameters
			if (!args.title) {
				return {
					content: [{
						type: 'text',
						text: 'Error: Task title is required'
					}],
					isError: true
				};
			}

			const taskData = {
				title: args.title,
				description: args.description,
				priority: args.priority || 'medium',
				due_date: args.due_date
			};

			const task = await this.trackexClient.createTask(trackexToken, taskData);
			
			return {
				content: [{
					type: 'text',
					text: `Task created successfully!\n\n` +
						  `ID: ${task.id}\n` +
						  `Title: ${task.title}\n` +
						  `Priority: ${task.priority}\n` +
						  `Status: ${task.status}\n` +
						  `Created: ${new Date(task.created_at).toLocaleString()}\n` +
						  (task.due_date ? `Due Date: ${task.due_date}\n` : '') +
						  (task.description ? `Description: ${task.description}` : '')
				}]
			};
		} catch (error) {
			return {
				content: [{
					type: 'text',
					text: `Error creating task: ${error instanceof Error ? error.message : 'Unknown error'}`
				}],
				isError: true
			};
		}
	}

	/**
	 * Format tasks response for display
	 */
	private formatTasksResponse(tasks: TrackexTask[], title: string): string {
		if (tasks.length === 0) {
			return `${title}: No tasks found.`;
		}

		let response = `${title} (${tasks.length} task${tasks.length > 1 ? 's' : ''}):\n\n`;
		
		tasks.forEach((task, index) => {
			response += `${index + 1}. **${task.title}** (ID: ${task.id})\n`;
			response += `   Priority: ${task.priority} | Status: ${task.status}\n`;
			if (task.due_date) {
				response += `   Due Date: ${task.due_date}\n`;
			}
			if (task.description) {
				response += `   Description: ${task.description}\n`;
			}
			response += `   Created: ${new Date(task.created_at).toLocaleString()}\n\n`;
		});

		return response;
	}

	/**
	 * Send SSE message
	 */
	private async sendSSEMessage(writer: WritableStreamDefaultWriter<Uint8Array>, message: SSEMessage): Promise<void> {
		const encoder = new TextEncoder();
		let sseData = '';
		
		if (message.id) {
			sseData += `id: ${message.id}\n`;
		}
		if (message.event) {
			sseData += `event: ${message.event}\n`;
		}
		if (message.retry) {
			sseData += `retry: ${message.retry}\n`;
		}
		
		sseData += `data: ${message.data}\n\n`;
		
		await writer.write(encoder.encode(sseData));
	}

	/**
	 * Create MCP error response
	 */
	private createMCPErrorResponse(id: string | number | null, code: MCPErrorCode, message: string): MCPResponse {
		const errorResponse = {
			jsonrpc: '2.0' as const,
			id: id,
			error: {
				code: code,
				message: message
			}
		};
		
		console.log('❌ Creating error response:', code, message);
		return errorResponse;
	}

	/**
	 * Create HTTP error response
	 */
	private createErrorResponse(
		id: string | number | null, 
		code: MCPErrorCode, 
		message: string, 
		corsHeaders: Record<string, string>
	): Response {
		const errorResponse: MCPResponse = {
			jsonrpc: '2.0',
			id: id,
			error: {
				code: code,
				message: message
			}
		};

		console.log('❌ Creating HTTP error response:', code, message);

		try {
			const responseBody = JSON.stringify(errorResponse);
			return new Response(responseBody, {
				status: 200, // Always return 200 for JSON-RPC errors per MCP spec
				headers: {
					'Content-Type': 'application/json',
					...corsHeaders,
				},
			});
		} catch (serializeError) {
			console.error('❌ Failed to serialize error response:', serializeError);
			// Fallback error response
			return new Response(JSON.stringify({
				jsonrpc: '2.0',
				id: id,
				error: {
					code: -32603,
					message: 'Internal error - failed to serialize response'
				}
			}), {
				status: 200,
				headers: {
					'Content-Type': 'application/json',
					...corsHeaders,
				},
			});
		}
	}
} 