import express, { Request, Response } from 'express';
import { Server } from 'http';
import open from 'open';
import { printMessage } from '../utils/printMessage.util';
import { serviceFactory } from '../config/service-factory';
import { getCognitoConfig, getPortalConfig } from '../config/cognito.config';

interface AuthCallbackData {
  code?: string;
  state?: string;
  error?: string;
}

interface AuthTokens {
  access_token: string;
  id_token: string;
  refresh_token: string;
  token_type: string;
  expires_in: number;
}

export class CognitoAuthService {
  private server: Server | null = null;
  private app: express.Application;
  private readonly portRange = { min: 8000, max: 8010 };

  private getUserFriendlyErrorMessage(error: any): string {
    const errorMessage = error?.message || error?.toString() || 'Unknown error';
    if (errorMessage.includes('fetch failed') || 
        errorMessage.includes('ECONNREFUSED') || 
        errorMessage.includes('ENOTFOUND') ||
        errorMessage.includes('ETIMEDOUT')) {
      return 'Unable to connect to the authentication server. Please check your internet connection and try again.';
    }
    if (errorMessage.includes('access_denied') || errorMessage.includes('user_cancelled')) {
      return 'Authentication was cancelled or denied. Please try signing in again.';
    }
    if (errorMessage.includes('invalid_grant') || errorMessage.includes('invalid_request')) {
      return 'The authentication request was invalid. Please try signing in again.';
    }
    if (errorMessage.includes('server_error') || errorMessage.includes('temporarily_unavailable')) {
      return 'The authentication service is temporarily unavailable. Please try again in a few moments.';
    }
    if (errorMessage.includes('Social sign-in failed')) {
      return 'Unable to complete social sign-in. Please try again or contact support if the issue persists.';
    }
    if (errorMessage.includes('Token exchange failed')) {
      return 'Unable to complete authentication. Please try signing in again.';
    }
    if (errorMessage.includes('Failed to') || 
        errorMessage.includes('Error:') || 
        errorMessage.includes('Exception:')) {
      return 'An unexpected error occurred during authentication. Please try again.';
    }
    return errorMessage;
  }

  private generateErrorPageHTML(errorMessage: string): string {
    return `
      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <meta name="viewport" content="width=device-width, initial-scale=1.0">
          <title>Authentication Error - Ordino CLI</title>
          <style>
              * { margin: 0; padding: 0; box-sizing: border-box; }
              body {
                  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
                  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                  min-height: 100vh;
                  display: flex;
                  align-items: center;
                  justify-content: center;
                  padding: 20px;
              }
              .container {
                  background: white;
                  border-radius: 16px;
                  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
                  padding: 48px;
                  text-align: center;
                  max-width: 500px;
                  width: 100%;
                  animation: slideUp 0.5s ease-out;
              }
              @keyframes slideUp {
                  from {
                      opacity: 0;
                      transform: translateY(20px);
                  }
                  to {
                      opacity: 1;
                      transform: translateY(0);
                  }
              }
              h1 { color: #1a202c; font-size: 28px; font-weight: 700; margin-bottom: 16px; }
              .error-message {
                  background: #fff5f5;
                  border-left: 4px solid #f5576c;
                  padding: 16px;
                  border-radius: 8px;
                  margin: 20px 0;
                  color: #c53030;
                  font-size: 14px;
              }
          </style>
      </head>
      <body>
          <div class="container">
              <h1>Authentication Error</h1>
              <p style="color: #4a5568; margin-bottom: 16px;">Something went wrong during authentication</p>
              <div class="error-message">
                  ${errorMessage}
              </div>
              <p style="color: #718096; font-size: 14px; margin-top: 20px;">
                  You can close this window and try again from the CLI
              </p>
          </div>
          <script>
              setTimeout(() => { window.close(); }, 5000);
          </script>
      </body>
      </html>
    `;
  }
  
  private async isPortAvailable(port: number): Promise<boolean> {
    return new Promise((resolve) => {
      try {
        const net = require('net');
        const testServer = net.createServer();
        testServer.listen(port, 'localhost', () => {
          testServer.close(() => resolve(true));
        });
        testServer.on('error', (error: any) => {
          resolve(false);
        });
      } catch (error) {
        resolve(false);
      }
    });
  }
  private currentPort: number | null = null;
  private authCompletePromise: Promise<AuthTokens | null> | null = null;
  private authCompleteResolver: ((value: AuthTokens | null) => void) | null = null;

  constructor() {
    this.app = express();
    this.setupRoutes();
  }

  private setupRoutes(): void {
    this.app.get('/callback', (req: Request, res: Response) => {
      const { code, state, error } = req.query as AuthCallbackData;

      if (error) {
        const userFriendlyError = this.getUserFriendlyErrorMessage({ message: error });
        res.send(this.generateErrorPageHTML(userFriendlyError));
        
        if (this.authCompleteResolver) {
          this.authCompleteResolver(null);
        }
        return;
      }

      if (code) {
        this.exchangeCodeForTokens(code)
          .then((tokens) => {
            res.send(`
              <!DOCTYPE html>
              <html lang="en">
              <head>
                  <meta charset="UTF-8">
                  <meta name="viewport" content="width=device-width, initial-scale=1.0">
                  <title>Authentication Successful - Ordino CLI</title>
                  <style>
                      * {
                          margin: 0;
                          padding: 0;
                          box-sizing: border-box;
                      }
                      
                      body {
                          font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
                          background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                          min-height: 100vh;
                          display: flex;
                          align-items: center;
                          justify-content: center;
                          padding: 20px;
                      }
                      
                      .container {
                          background: white;
                          border-radius: 16px;
                          box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
                          padding: 48px;
                          text-align: center;
                          max-width: 500px;
                          width: 100%;
                          animation: slideUp 0.5s ease-out;
                      }
                      
                      @keyframes slideUp {
                          from {
                              opacity: 0;
                              transform: translateY(20px);
                          }
                          to {
                              opacity: 1;
                              transform: translateY(0);
                          }
                      }
                      
                      h1 {
                          color: #1a202c;
                          font-size: 28px;
                          font-weight: 700;
                          margin-bottom: 16px;
                      }
                      
                      .message {
                          color: #4a5568;
                          font-size: 16px;
                          line-height: 1.6;
                          margin-bottom: 24px;
                      }
                      
                      .cli-message {
                          background: #f7fafc;
                          border-left: 4px solid #667eea;
                          padding: 16px;
                          border-radius: 8px;
                          margin-bottom: 24px;
                      }
                      
                      .cli-message p {
                          color: #2d3748;
                          font-size: 15px;
                          font-weight: 500;
                      }
                  </style>
              </head>
              <body>
                  <div class="container">
                      <h1>Authentication Successful!</h1>
                      <div class="message">
                          Your authentication has been completed successfully.
                      </div>
                      <div class="cli-message">
                          <p>🎉 You're all set!</p>
                          <p style="margin-top: 8px; font-weight: 400; font-size: 14px;">
                              You can now close this window and return to the CLI
                          </p>
                      </div>
                  </div>
                  <script>
                      setTimeout(() => {
                          window.close();
                      }, 3000);
                  </script>
              </body>
              </html>
            `);
            
            if (this.authCompleteResolver) {
              this.authCompleteResolver(tokens);
            }
          })
          .catch((error) => {
            const userFriendlyError = this.getUserFriendlyErrorMessage(error);
            res.send(this.generateErrorPageHTML(userFriendlyError));
            
            if (this.authCompleteResolver) {
              this.authCompleteResolver(null);
            }
          });
      } else {
        res.send(`
          <!DOCTYPE html>
          <html lang="en">
          <head>
              <meta charset="UTF-8">
              <meta name="viewport" content="width=device-width, initial-scale=1.0">
              <title>Authentication Failed - Ordino CLI</title>
              <style>
                  * { margin: 0; padding: 0; box-sizing: border-box; }
                  body {
                      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
                      background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
                      min-height: 100vh;
                      display: flex;
                      align-items: center;
                      justify-content: center;
                      padding: 20px;
                  }
                  .container {
                      background: white;
                      border-radius: 16px;
                      box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
                      padding: 48px;
                      text-align: center;
                      max-width: 500px;
                      width: 100%;
                      animation: slideUp 0.5s ease-out;
                  }
                  @keyframes slideUp {
                      from {
                          opacity: 0;
                          transform: translateY(20px);
                      }
                      to {
                          opacity: 1;
                          transform: translateY(0);
                      }
                  }
                  h1 { color: #1a202c; font-size: 28px; font-weight: 700; margin-bottom: 16px; }
              </style>
          </head>
          <body>
              <div class="container">
                  <h1>Authentication Failed</h1>
                  <p style="color: #4a5568; margin: 16px 0;">No authorization code was received from the authentication provider.</p>
                  <p style="color: #718096; font-size: 14px;">
                      Please try signing in again from the CLI
                  </p>
              </div>
              <script>
                  setTimeout(() => { window.close(); }, 3000);
              </script>
          </body>
          </html>
        `);
        
        if (this.authCompleteResolver) {
          this.authCompleteResolver(null);
        }
      }
    });
    this.app.get('/health', (req: Request, res: Response) => {
      res.json({ status: 'ok', message: 'Auth server is running' });
    });
  }

  private async exchangeCodeForTokens(code: string): Promise<AuthTokens | null> {
    try {
      const currentEnvironment = serviceFactory.getEnvironment();
      const cognitoConfig = getCognitoConfig(currentEnvironment);
      
      if (!code || code.length < 10) {
        throw new Error('Invalid authorization code received');
      }
      
      const redirectUri = this.currentPort 
        ? `http://localhost:${this.currentPort}/callback`
        : cognitoConfig.redirectUri;
      
      const requestBody = new URLSearchParams({
        grant_type: 'authorization_code',
        client_id: cognitoConfig.clientId,
        code: code,
        redirect_uri: redirectUri,
      });
      
      if (cognitoConfig.clientSecret) {
        requestBody.append('client_secret', cognitoConfig.clientSecret);
      }
      
      const response = await fetch(cognitoConfig.tokenEndpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
        },
        body: requestBody,
      });

      if (!response.ok) {
        const errorText = await response.text();
        try {
          const errorData = JSON.parse(errorText);
          const errorMessage = errorData.error || errorText;
          throw new Error(`Token exchange failed: ${errorMessage}`);
        } catch {
          throw new Error(`Token exchange failed: ${response.status} ${response.statusText} - ${errorText}`);
        }
      }

      const tokens: AuthTokens = await response.json();
      return tokens;
    } catch (error) {
      throw error;
    }
  }

  private startServer(): Promise<void> {
    return new Promise(async (resolve, reject) => {
      try {
        let availablePort: number | null = null;
        for (let port = this.portRange.min; port <= this.portRange.max; port++) {
          if (await this.isPortAvailable(port)) {
            availablePort = port;
            break;
          }
        }
        if (!availablePort) {
          reject(new Error(`No available ports found in range ${this.portRange.min}-${this.portRange.max}. Please close other applications using these ports.`));
          return;
        }
        
        this.server = this.app.listen(availablePort, 'localhost', () => {
          this.currentPort = availablePort;
          resolve();
        });

        this.server.on('error', (error: any) => {
          reject(error);
        });
      } catch (error) {
        reject(error);
      }
    });
  }

  private stopServer(): Promise<void> {
    return new Promise((resolve) => {
      if (this.server) {
        const timeoutId = setTimeout(() => {
          this.server = null;
          this.currentPort = null;
          resolve();
        }, 2000);
        
        this.server.close(() => {
          clearTimeout(timeoutId);
          this.server = null;
          this.currentPort = null;
          resolve();
        });
      } else {
        resolve();
      }
    });
  }

  private buildAuthUrl(): string {
    const currentEnvironment = serviceFactory.getEnvironment();
    const cognitoConfig = getCognitoConfig(currentEnvironment);
    const authEndpoint = `https://${cognitoConfig.domain}/login`;
    const redirectUri = this.currentPort 
      ? `http://localhost:${this.currentPort}/callback`
      : cognitoConfig.redirectUri;
    const params = new URLSearchParams({
      client_id: cognitoConfig.clientId,
      response_type: 'code',
      scope: 'email openid phone profile',
      redirect_uri: redirectUri,
      state: Math.random().toString(36).substring(2, 15),
    });

    return `${authEndpoint}?${params.toString()}`;
  }

  public async authenticateWithBrowser(): Promise<AuthTokens | null> {
    try {
      await this.startServer();
      this.authCompletePromise = new Promise((resolve) => {
        this.authCompleteResolver = resolve;
      });
      const authUrl = this.buildAuthUrl();
      printMessage('🌐 Opening browser for authentication...', "36", false, false);
      printMessage(`If the browser doesn't open automatically, please visit: ${authUrl}`, "33", false, false);
      try {
        await open(authUrl);
      } catch (error) {
        printMessage('Failed to open browser automatically. Please copy and paste the URL above into your browser.', "31", false, false);
      }
      const timeoutPromise = new Promise<AuthTokens | null>((resolve) => {
        setTimeout(() => {
          printMessage('⏰ Authentication timeout after 5 minutes', "31", false, false);
          resolve(null);
        }, 5 * 60 * 1000);
      });
      const result = await Promise.race([this.authCompletePromise, timeoutPromise]);

      return result;
    } catch (error) {
      const userFriendlyError = this.getUserFriendlyErrorMessage(error);
      printMessage(`❌ Authentication error: ${userFriendlyError}`, "31", false, false);
      return null;
    } finally {
      await this.stopServer();
      this.authCompletePromise = null;
      this.authCompleteResolver = null;
    }
  }

  public async checkAuthStatus(): Promise<boolean> {
    return false;
  }

  private isOAuthLogin(idToken: string): boolean {
    try {
      const tokenParts = idToken.split('.');
      if (tokenParts.length === 3) {
        const payload = JSON.parse(Buffer.from(tokenParts[1], 'base64').toString());
        return payload.identities && Array.isArray(payload.identities) && payload.identities.length > 0;
      }
      return false;
    } catch {
      return true;
    }
  }

  private async getUserInfo(accessToken: string): Promise<any> {
    const currentEnvironment = serviceFactory.getEnvironment();
    const cognitoConfig = getCognitoConfig(currentEnvironment);
    const response = await fetch(`https://${cognitoConfig.domain}/oauth2/userInfo`, {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    if (!response.ok) {
      throw new Error('Failed to fetch user info');
    }
    return await response.json();
  }

  private async socialSignIn(userInfo: any, idToken: string): Promise<string | null> {
    const currentEnvironment = serviceFactory.getEnvironment();
    const portalConfig = getPortalConfig(currentEnvironment);
    const socialSignInData = {
      Email: userInfo.email,
      FirstName: userInfo.given_name || userInfo.name || 'User',
      LastName: userInfo.family_name || userInfo.given_name || 'User',
    };
    const response = await fetch(`${portalConfig.baseUrl}/api/v1/social-signin`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${idToken}`
      },
      body: JSON.stringify(socialSignInData)
    });
    if (!response.ok) {
      const errorText = await response.text();
      const technicalError = `Social sign-in failed: ${response.status} ${response.statusText} - ${errorText}`;
      const userFriendlyError = this.getUserFriendlyErrorMessage({ message: technicalError });
      throw new Error(userFriendlyError);
    }
    const result = await response.json();
    if (result.isSuccess && result.extraInfo) {
      return typeof result.extraInfo === 'string' ? result.extraInfo : result.extraInfo.idToken;
    } else {
      throw new Error(result.message || 'Failed to get backend token');
    }
  }

  private async getCliApiKey(token: string): Promise<string | null> {
    const currentEnvironment = serviceFactory.getEnvironment();
    const portalConfig = getPortalConfig(currentEnvironment);
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 10000);
    try {
      const response = await fetch(`${portalConfig.baseUrl}/api/v1/system-api-key/cli-key`, {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${token}`,
          'Content-Type': 'application/json'
        },
        signal: controller.signal
      });
      clearTimeout(timeoutId);
      if (!response.ok) {
        const errorText = await response.text();
        throw new Error(`Failed to generate CLI API key: ${response.status} ${response.statusText} - ${errorText}`);
      }
      const result = await response.json();
      if (result.isSuccess && result.extraInfo) {
        return typeof result.extraInfo === 'string' ? result.extraInfo : result.extraInfo.accessToken;
      } else {
        throw new Error(result.message || 'Failed to generate CLI API key');
      }
    } catch (error) {
      clearTimeout(timeoutId);
      if (error instanceof Error) {
        if (error.message.includes('ECONNREFUSED') || 
            error.message.includes('fetch failed') ||
            error.message.includes('aborted')) {
          return null;
        }
      }
      throw error;
    }
  }

  public async authenticate(): Promise<AuthTokens | null> {
    const isAuthenticated = await this.checkAuthStatus();
    if (isAuthenticated) {
      printMessage('✅ Already authenticated', "32", false, false);
      return null;
    }
    const tokens = await this.authenticateWithBrowser();
    if (tokens) {
      printMessage('✅ Authentication successful!', "32", false, false);
      try {
        const isOAuthLogin = this.isOAuthLogin(tokens.id_token);
        if (isOAuthLogin) {
          const userInfo = await this.getUserInfo(tokens.access_token);
          if (userInfo) {
            const backendToken = await this.socialSignIn(userInfo, tokens.id_token);
            if (backendToken) {
              const cliApiKey = await this.getCliApiKey(backendToken);
              if (cliApiKey) {
                (tokens as any).cliApiKey = cliApiKey;
              }
            }
          }
        } else {
          const cliApiKey = await this.getCliApiKey(tokens.id_token);
          if (cliApiKey) {
            (tokens as any).cliApiKey = cliApiKey;
          }
        }
      } catch (error) {
        const userFriendlyError = this.getUserFriendlyErrorMessage(error);
        printMessage(`⚠️ Warning: ${userFriendlyError}`, "33", false, false);
      }
      return tokens;
    } else {
      printMessage('❌ Authentication failed or was cancelled', "31", false, false);
      return null;
    }
  }
}

export const cognitoAuthService = new CognitoAuthService();
