/**
 * Trackex MCP Server - Cloudflare Worker with OAuth Provider
 * 
 * This worker implements the Model Context Protocol (MCP) specification
 * with OAuth authentication using @cloudflare/workers-oauth-provider
 */

import { OAuthProvider } from '@cloudflare/workers-oauth-provider';
import { WorkerEntrypoint } from 'cloudflare:workers';
import { MCPServerHandler } from './mcp-server-handler';
import { TrackexAPIClient } from './trackex-api-client';
import { CustomTokenHandler } from './custom-token-handler';

// Environment interface for type safety
export interface Env {
	// OAuth Configuration
	MICROSOFT_CLIENT_ID: string;
	MICROSOFT_CLIENT_SECRET: string;
	MICROSOFT_TENANT_ID: string;
	
	// Trackex API Configuration
	TRACKEX_API_BASE_URL: string;
	TRACKEX_MOCK_MODE?: string; // 'true' to enable mock mode for development/testing
	
	// MCP Configuration
	MCP_SERVER_NAME: string;
	MCP_SERVER_VERSION: string;
	MCP_SERVER_URL: string;
	
	// CORS Origins
	ALLOWED_ORIGINS: string;
	
	// KV Storage for OAuth (required by workers-oauth-provider)
	OAUTH_KV: KVNamespace;
	
	// OAuth Provider instance (injected by workers-oauth-provider)
	OAUTH_PROVIDER: any;
}

// Microsoft OAuth token response interface
interface MicrosoftTokenResponse {
	access_token: string;
	token_type: string;
	expires_in: number;
	scope: string;
	refresh_token?: string;
}

// Microsoft user info interface
interface MicrosoftUserInfo {
	id: string;
	displayName: string;
	mail?: string;
	userPrincipalName: string;
}

// Default handler for non-API requests (OAuth authorization flow)
const defaultHandler = {
	fetch: async (request: any, env: any, ctx: any) => {
		const url = new URL(request.url);
		
		// Comprehensive CORS headers for all responses
		const corsHeaders = {
			'Access-Control-Allow-Origin': '*',
			'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
			'Access-Control-Allow-Headers': 'Content-Type, Authorization',
			'Access-Control-Max-Age': '86400',
		};
		
		// Handle CORS preflight requests
		if (request.method === 'OPTIONS') {
			return new Response(null, {
				status: 204,
				headers: corsHeaders,
			});
		}



		if (url.pathname === '/authorize') {
			// Handle OAuth authorization flow
			// 
			// Claude.ai OAuth callback URL: https://claude.ai/api/mcp/auth_callback
			// This redirect URI is provided by Claude.ai in the OAuth authorization request
			// and should be stored in stateData.redirect_uri for the final redirect
			try {
				// Parse the OAuth authorization request
				const oauthReqInfo = await env.OAUTH_PROVIDER.parseAuthRequest(request);
				
				// Log the redirect URI from Claude.ai for debugging
				console.log('🔐 OAuth authorization request received');
				console.log('   Client ID:', oauthReqInfo.clientId);
				console.log('   Redirect URI:', oauthReqInfo.redirectUri);
				console.log('   Expected Claude.ai callback: https://claude.ai/api/mcp/auth_callback');
				
				// Validate that the redirect URI is Claude.ai's callback URL
				// Claude.ai uses: https://claude.ai/api/mcp/auth_callback
				if (oauthReqInfo.redirectUri && !oauthReqInfo.redirectUri.includes('claude.ai/api/mcp/auth_callback')) {
					console.warn('⚠️ Warning: Unexpected redirect URI:', oauthReqInfo.redirectUri);
					console.warn('   Expected: https://claude.ai/api/mcp/auth_callback');
				}
				
				// Look up client information
				const clientInfo = await env.OAUTH_PROVIDER.lookupClient(oauthReqInfo.clientId);
				
				// For our MCP server, we'll auto-approve the authorization
				// In a real app, you'd show a consent UI here
				
				// Redirect to Microsoft OAuth
				const microsoftAuthUrl = new URL('https://login.microsoftonline.com/common/oauth2/v2.0/authorize');
				microsoftAuthUrl.searchParams.set('client_id', env.MICROSOFT_CLIENT_ID);
				microsoftAuthUrl.searchParams.set('response_type', 'code');
				microsoftAuthUrl.searchParams.set('redirect_uri', `${url.origin}/callback`);
				microsoftAuthUrl.searchParams.set('scope', 'openid profile email');
				microsoftAuthUrl.searchParams.set('state', JSON.stringify({
					oauth_state: oauthReqInfo.state,
					client_id: oauthReqInfo.clientId,
					// redirect_uri: oauthReqInfo.redirectUri, // Store Claude.ai's callback URL
					redirect_uri: 'https://claude.ai/api/mcp/auth_callback',
					scope: oauthReqInfo.scope
				}));
				
				console.log('✅ Redirecting to Microsoft OAuth, will redirect back to:', oauthReqInfo.redirectUri);
				
				return Response.redirect(microsoftAuthUrl.toString(), 302);
			} catch (error) {
				console.error('Authorization error:', error);
				return new Response('Authorization error', { 
					status: 400,
					headers: corsHeaders
				});
			}
		}

		if (url.pathname === '/callback') {
			// Handle Microsoft OAuth callback
			try {
				const code = url.searchParams.get('code');
				const state = url.searchParams.get('state');
				
				if (!code || !state) {
					return new Response('Missing code or state', { status: 400 });
				}

				const stateData = JSON.parse(state);
				
				// Exchange code for Microsoft tokens
				const tokenResponse = await fetch('https://login.microsoftonline.com/common/oauth2/v2.0/token', {
					method: 'POST',
					headers: {
						'Content-Type': 'application/x-www-form-urlencoded',
					},
					body: new URLSearchParams({
						client_id: env.MICROSOFT_CLIENT_ID,
						client_secret: env.MICROSOFT_CLIENT_SECRET,
						code: code,
						grant_type: 'authorization_code',
						redirect_uri: `${url.origin}/callback`,
					}),
				});

				if (!tokenResponse.ok) {
					throw new Error('Failed to exchange code for tokens');
				}

				const tokens: MicrosoftTokenResponse = await tokenResponse.json();

				// Get user info from Microsoft
				const userResponse = await fetch('https://graph.microsoft.com/v1.0/me', {
					headers: {
						'Authorization': `Bearer ${tokens.access_token}`,
					},
				});

				if (!userResponse.ok) {
					throw new Error('Failed to get user info');
				}

				const userInfo: MicrosoftUserInfo = await userResponse.json();

				// Create Trackex session
				const trackexClient = new TrackexAPIClient(env);
				const trackexSession = await trackexClient.createSession(tokens.access_token, userInfo);

				// Generate authorization code for our custom token handler
				const authCode = crypto.randomUUID();
				
				// Store authorization data in KV for custom token handler
				const authData = {
					clientId: stateData.client_id,
					userId: userInfo.id,
					scope: ['mcp:read', 'mcp:write'],
					expiresAt: Date.now() + (10 * 60 * 1000), // 10 minutes
					props: {
						userId: userInfo.id,
						email: userInfo.mail || userInfo.userPrincipalName,
						name: userInfo.displayName,
						trackexToken: trackexSession.token,
						trackexUserId: trackexSession.userId,
					},
				};

				await env.OAUTH_KV.put(`auth_code:${authCode}`, JSON.stringify(authData), {
					expirationTtl: 600, // 10 minutes
				});

				// Build redirect URL with authorization code
				// This should redirect to Claude.ai's callback: https://claude.ai/api/mcp/auth_callback
				console.log('🔄 Redirecting back to Claude.ai callback:', stateData.redirect_uri);
				const redirectUrl = new URL(stateData.redirect_uri);
				redirectUrl.searchParams.set('code', authCode);
				redirectUrl.searchParams.set('state', stateData.oauth_state);

				const redirectTo = redirectUrl.toString();
				console.log('✅ Final redirect URL:', redirectTo);

				return Response.redirect(redirectTo, 302);
			} catch (error) {
				console.error('Callback error:', error);
				return new Response('Callback error', { 
					status: 500,
					headers: corsHeaders
				});
			}
		}

		// Default response for unknown paths
		return new Response('Not found', { 
			status: 404,
			headers: corsHeaders
		});
	},
};

// API handler for MCP requests (requires valid OAuth token)
class MCPApiHandler extends WorkerEntrypoint<Env> {
	async fetch(request: Request): Promise<Response> {
		// The OAuth provider has already validated the access token
		// and provided the user's props in this.ctx.props
		
		const mcpHandler = new MCPServerHandler(this.env);
		
		// Convert the OAuth props to our AuthContext format
		const authContext = {
			isAuthenticated: true,
			userId: this.ctx.props.userId,
			trackexToken: this.ctx.props.trackexToken,
			userInfo: {
				email: this.ctx.props.email,
				name: this.ctx.props.name,
				tenantId: this.ctx.props.userId, // Using userId as tenantId for now
			},
		};

		// Handle CORS
		const corsHeaders = {
			'Access-Control-Allow-Origin': '*',
			'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
			'Access-Control-Allow-Headers': 'Content-Type, Authorization',
		};

		return mcpHandler.handleRequest(request, authContext, corsHeaders);
	}
}

// Custom token validation middleware for MCP API requests
async function validateCustomToken(request: Request, env: Env): Promise<any> {
	const authHeader = request.headers.get('Authorization');
	if (!authHeader || !authHeader.startsWith('Bearer ')) {
		throw new Error('Missing or invalid access token');
	}

	const accessToken = authHeader.substring(7);
	const tokenKey = `access_token:${accessToken}`;
	const tokenData = await env.OAUTH_KV.get(tokenKey, 'json') as any;
	
	if (!tokenData) {
		throw new Error('Invalid access token');
	}

	// Check expiration
	if (tokenData.expiresAt && Date.now() > tokenData.expiresAt) {
		await env.OAUTH_KV.delete(tokenKey);
		throw new Error('Expired access token');
	}

	return tokenData;
}

// Main worker entry point
const worker = {
	async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
		const url = new URL(request.url);

		// Comprehensive CORS headers for all responses
		const corsHeaders = {
			'Access-Control-Allow-Origin': '*',
			'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
			'Access-Control-Allow-Headers': 'Content-Type, Authorization',
			'Access-Control-Max-Age': '86400',
		};

		// Handle CORS preflight requests
		if (request.method === 'OPTIONS') {
			return new Response(null, {
				status: 204,
				headers: corsHeaders,
			});
		}

		// Handle OAuth discovery endpoint - updated for Claude.ai compatibility
		if (url.pathname === '/.well-known/oauth-authorization-server') {
			console.log('🔍 OAuth discovery endpoint requested');
			return new Response(JSON.stringify({
				issuer: url.origin,
				authorization_endpoint: `${url.origin}/authorize`,
				token_endpoint: `${url.origin}/oauth/token`,
				registration_endpoint: `${url.origin}/oauth/register`,
				scopes_supported: ['mcp:read', 'mcp:write'],
				response_types_supported: ['code'],
				response_modes_supported: ['query'],
				grant_types_supported: ['authorization_code'],
				token_endpoint_auth_methods_supported: ['none'],
				code_challenge_methods_supported: ['S256'],
				// Updated MCP endpoint path for Claude.ai
				mcp_endpoint: `${url.origin}/mcp`,
				api_endpoint: `${url.origin}/mcp`,
				// Server info for Claude.ai
				server_info: {
					name: env.MCP_SERVER_NAME || "Trackex MCP Server",
					version: env.MCP_SERVER_VERSION || "1.0.0",
					description: "MCP Server for Trackex Task Management Integration with Microsoft Authentication"
				},
				// Capabilities for Claude.ai
				capabilities: {
					tools: {
						listChanged: false
					},
					logging: {},
					prompts: {
						listChanged: false
					},
					resources: {
						subscribe: false,
						listChanged: false
					}
				}
			}), {
				headers: { 
					'Content-Type': 'application/json',
					...corsHeaders
				},
			});
		}

		// Handle new MCP capability endpoint for Claude.ai
		if (url.pathname === '/.well-known/mcp') {
			console.log('🔍 MCP capability discovery endpoint requested');
			return new Response(JSON.stringify({
				protocol_version: "2024-11-05",
				server_info: {
					name: env.MCP_SERVER_NAME || "Trackex MCP Server",
					version: 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
					}
				},
				transport: {
					type: "sse",
					endpoint: `${url.origin}/mcp`
				},
				auth: {
					type: "oauth2",
					authorization_endpoint: `${url.origin}/authorize`,
					token_endpoint: `${url.origin}/oauth/token`,
					scopes: ['mcp:read', 'mcp:write']
				}
			}), {
				headers: { 
					'Content-Type': 'application/json',
					...corsHeaders
				},
			});
		}

		// Intercept token endpoint BEFORE delegating to OAuth provider
		if (url.pathname === '/oauth/token' && request.method === 'POST') {
			console.log('🔧 Using custom token handler for public client support');
			const customTokenHandler = new CustomTokenHandler(env);
			const response = await customTokenHandler.handleTokenRequest(request);
			
			// Add CORS headers to token response
			const newHeaders = new Headers(response.headers);
			Object.entries(corsHeaders).forEach(([key, value]) => {
				newHeaders.set(key, value);
			});
			
			return new Response(response.body, {
				status: response.status,
				statusText: response.statusText,
				headers: newHeaders
			});
		}

		// Handle direct tools endpoint for debugging
		if (url.pathname === '/tools' && request.method === 'GET') {
			console.log('🛠️ Direct tools endpoint requested');
			const mcpHandler = new MCPServerHandler(env);
			return mcpHandler.handleDirectToolsRequest(corsHeaders);
		}

		// Handle MCP API requests - PRIMARY endpoint is now /mcp, with /sse as fallback
		if (url.pathname.startsWith('/mcp') || url.pathname.startsWith('/sse') || url.pathname === '/') {
			console.log('🔗 MCP request received:', request.method, url.pathname);
			
			// Check if this is a request that requires authentication
			let requiresAuth = true;
			
			// Only try to parse body for POST requests
			if (request.method === 'POST') {
				try {
					const body = await request.text();
					console.log('📥 Request body:', body);
					
					const mcpRequest = JSON.parse(body);
					console.log('🔍 Parsed MCP method:', mcpRequest.method);
					
					// These methods don't require authentication - EXPANDED LIST
					if (mcpRequest.method === 'initialize' || 
					    mcpRequest.method === 'initialized' || 
					    mcpRequest.method === 'notifications/initialized' ||
					    mcpRequest.method === 'tools/list' ||
					    mcpRequest.method === 'capabilities') {
						requiresAuth = false;
						console.log('✅ Method does not require auth:', mcpRequest.method);
					} else {
						console.log('🔐 Method requires authentication:', mcpRequest.method);
					}
					
					// Recreate the request with the body for downstream processing
					request = new Request(request.url, {
						method: request.method,
						headers: request.headers,
						body: body,
					});
				} catch (error) {
					console.error('❌ Failed to parse MCP request body:', error);
					// If we can't parse the request, assume it needs auth
					requiresAuth = true;
				}
			} else if (request.method === 'GET') {
				console.log('📡 GET request received');
				// For Claude.ai Remote MCP Server, GET requests are for capabilities discovery
				// We don't need persistent SSE connections for remote MCP servers
				requiresAuth = false;
				console.log('✅ GET request for capabilities - no auth required');
			}

			let authContext;
			if (requiresAuth) {
				try {
					console.log('🔐 Validating authentication token...');
					const tokenData = await validateCustomToken(request, env);
					authContext = {
						isAuthenticated: true,
						userId: tokenData.userId,
						trackexToken: tokenData.props.trackexToken,
						userInfo: {
							email: tokenData.props.email,
							name: tokenData.props.name,
							tenantId: tokenData.userId,
						},
					};
					console.log('✅ Authentication successful for user:', tokenData.props.email);
				} catch (error) {
					console.error('❌ Authentication failed:', error);
					return new Response(JSON.stringify({
						jsonrpc: '2.0',
						id: null,
						error: {
							code: -32001,
							message: error instanceof Error ? error.message : 'Authentication failed'
						}
					}), {
						status: 200, // Return 200 for JSON-RPC errors
						headers: { 
							'Content-Type': 'application/json',
							...corsHeaders
						},
					});
				}
			} else {
				// Unauthenticated context for initialize/initialized methods
				console.log('✅ No authentication required for this method');
				authContext = {
					isAuthenticated: false,
				};
			}

			console.log('🚀 Delegating to MCP handler...');
			const mcpHandler = new MCPServerHandler(env);
			return mcpHandler.handleRequest(request, authContext, corsHeaders);
		}

		// Delegate other requests to the OAuth provider (discovery, registration, authorization)
		const oauthProvider = new OAuthProvider({
			apiRoute: '/mcp', // Updated to use /mcp as primary endpoint
			apiHandler: MCPApiHandler, // Not used since we handle /mcp above
			// @ts-ignore - Type compatibility issue
			defaultHandler: defaultHandler,
			authorizeEndpoint: '/authorize',
			tokenEndpoint: '/oauth/token', // We override this in defaultHandler
			clientRegistrationEndpoint: '/oauth/register',
			scopesSupported: ['mcp:read', 'mcp:write'],
			disallowPublicClientRegistration: false,
		});

		// @ts-ignore - Type compatibility issue
		return oauthProvider.fetch(request, env, ctx);
	}
};

export default worker as any;
