import { chromium, Browser, Page } from 'playwright';
import express from 'express';
import { Server } from 'http';
import path from 'path';
import dotenv from 'dotenv';
import { config } from 'dotenv';
import { AtProtoOAuthProcessor } from '../../../atproto/infrastructure/services/AtProtoOAuthProcessor';
import { InitiateOAuthSignInUseCase } from '../../application/use-cases/InitiateOAuthSignInUseCase';
import { CompleteOAuthSignInUseCase } from '../../application/use-cases/CompleteOAuthSignInUseCase';
import { OAuthClientFactory } from '../../infrastructure/services/OAuthClientFactory';
import { InMemoryUserRepository } from '../infrastructure/InMemoryUserRepository';
import {
  JwtTokenService,
  UserAuthenticationService,
} from '../../infrastructure';
import { InMemoryTokenRepository } from '../infrastructure/InMemoryTokenRepository';

// Load environment variables
dotenv.config();
// Load test environment variables
config({ path: '.env.test' });

// Get test credentials from environment
const TEST_HANDLE = process.env.TEST_BLUESKY_HANDLE;
const TEST_PASSWORD = process.env.TEST_BLUESKY_PASSWORD;

if (!TEST_HANDLE || !TEST_PASSWORD) {
  console.error(
    'TEST_BLUESKY_HANDLE and TEST_BLUESKY_PASSWORD must be set in .env.test',
  );
  process.exit(1);
}

describe('OAuth Sign-In Flow', () => {
  let browser: Browser;
  let page: Page;
  let server: Server;
  const PORT = 3001;
  const BASE_URL = `http://127.0.0.1:${PORT}`;

  // This test requires manual interaction
  // Set a longer timeout for manual testing
  jest.setTimeout(5 * 60 * 1000); // 5 minutes
  let oauthTokens: any = null;

  beforeAll(async () => {
    // Create OAuth client using the factory with in-memory stores
    const oauthClient = OAuthClientFactory.createInMemoryClient(
      `${BASE_URL}/api/user`,
      'Annos Test App',
    );

    // Create OAuth processor with the client
    const oauthProcessor = new AtProtoOAuthProcessor(oauthClient);

    // Create in-memory services
    const userRepository = InMemoryUserRepository.getInstance();
    const tokenRepository = InMemoryTokenRepository.getInstance();
    const tokenService = new JwtTokenService(tokenRepository, 'test-secret');
    const userAuthService = new UserAuthenticationService(userRepository);

    // Create use cases
    const initiateOAuthSignInUseCase = new InitiateOAuthSignInUseCase(
      oauthProcessor,
    );

    const completeOAuthSignInUseCase = new CompleteOAuthSignInUseCase(
      oauthProcessor,
      tokenService,
      userRepository,
      userAuthService,
    );

    // Start a simple express server to serve our test page
    const app = express();
    app.use(express.static(path.join(__dirname, 'public')));

    // Real API endpoints using our actual implementations
    app.get('/api/user/login', async (req, res) => {
      const handle = req.query.handle;

      const result = await initiateOAuthSignInUseCase.execute({
        handle: handle as string | undefined,
      });

      if (result.isErr()) {
        res.status(400).json({ error: result.error });
      }

      res.json({ authUrl: result.unwrap().authUrl });
    });

    // Store tokens globally so we can access them in the test

    app.get('/api/user/oauth/callback', async (req, res) => {
      const { code, state, iss } = req.query;

      if (!code || !state || !iss) {
        res.status(400).json({ error: 'Missing required parameters' });
        return;
      }

      // Use the CompleteOAuthSignInUseCase to process the callback
      const result = await completeOAuthSignInUseCase.execute({
        code: code as string,
        state: state as string,
        iss: iss as string,
      });

      if (result.isErr()) {
        res.status(400).json({ error: result.error });
        return;
      }

      // Store tokens for test verification
      oauthTokens = result.unwrap();

      // Return the tokens
      res.json({
        message: 'Authentication successful',
        tokens: oauthTokens,
        testComplete: true,
      });
    });

    server = app.listen(PORT);

    // Launch browser
    browser = await chromium.launch({ headless: false });
    page = await browser.newPage();
  });

  afterAll(async () => {
    await browser.close();
    server.close();
  });

  it('should complete the OAuth sign-in flow automatically', async () => {
    // Navigate to our test page
    await page.goto(`${BASE_URL}/oauth-test.html`);

    // Enter the Bluesky handle from environment variables
    await page.fill('#handle-input', TEST_HANDLE);

    // Click the login button
    await page.click('#login-button');

    // Wait for navigation to the Bluesky OAuth page
    await page.waitForNavigation();

    // We should now be on the Bluesky login page
    // Wait for the login form to be fully loaded
    await page.waitForLoadState('networkidle');

    // The page is a React app, so we need to wait for elements to be available
    // Only wait for password field since the identifier is pre-filled
    await page.waitForSelector('input[type="password"]', { timeout: 10000 });

    // Enter password (identifier is already filled)
    await page.fill('input[type="password"]', TEST_PASSWORD);

    // Click the login/authorize button - using a more specific selector
    // In React apps, sometimes we need to wait a bit after filling inputs
    await page.waitForTimeout(1000);

    // Find and click the submit button
    try {
      // Take a screenshot before clicking to help with debugging
      await page.screenshot({ path: 'before-login-click.png' });

      // Try different possible selectors for the submit button
      const buttonSelectors = [
        'button[type="submit"]',
        'button:has-text("Sign in")',
        'button:has-text("Continue")',
        'button:has-text("Authorize")',
        'button.primary',
        'button[aria-label="Sign in"]',
        'form button',
        // More specific selectors based on the HTML structure
        'div > button',
        'form > div > button',
      ];

      let buttonClicked = false;
      for (const selector of buttonSelectors) {
        const button = await page.$(selector);
        if (button) {
          // Check if button is visible and enabled
          const isVisible = await button.isVisible();
          if (isVisible) {
            await button.click();
            console.log(`Clicked button with selector: ${selector}`);
            buttonClicked = true;
            break;
          }
        }
      }

      if (!buttonClicked) {
        console.log(
          'No button found with standard selectors, trying JavaScript click',
        );
        // Try clicking any button that looks like a submit button using JavaScript
        await page.evaluate(() => {
          // @ts-ignore
          const buttons = Array.from(document.querySelectorAll('button'));
          const loginButton = buttons.find(
            (button: any) =>
              button.innerText.includes('Sign in') ||
              button.innerText.includes('Continue') ||
              button.innerText.includes('Log in') ||
              button.innerText.includes('Authorize'),
          );
          // @ts-ignore
          if (loginButton) loginButton.click();
        });
      }
    } catch (error) {
      console.error('Failed to find or click the submit button:', error);
      // Take a screenshot to debug
      await page.screenshot({ path: 'login-page-debug.png' });
      throw error;
    }

    // Wait for redirect back to our callback page
    // Use a more robust approach to handle potential redirects
    try {
      // Wait for any navigation to complete
      await page.waitForNavigation({ timeout: 1000 }).catch((e) => {
        console.log('Initial navigation timeout, continuing...');
      });

      // Take a screenshot after login attempt
      await page.screenshot({ path: 'after-login-click.png' });

      // Check if we're on the right page
      const currentUrl = page.url();
      console.log(`Current page URL: ${currentUrl}`);

      // If we're not on our callback page yet, we might need to authorize
      if (!currentUrl.includes('127.0.0.1:3001')) {
        console.log(
          'Not yet redirected to callback, looking for authorize button...',
        );

        // Wait a shorter time for page transitions
        await page.waitForTimeout(500);

        // Take another screenshot to see the current state
        await page.screenshot({ path: 'authorize-page.png' });

        // Wait for the authorization page to fully load, but with shorter timeouts
        await page.waitForLoadState('networkidle', { timeout: 5000 });
        await page.waitForTimeout(1000); // Give React app time to render

        // Try to find and click an authorize button if present
        const authorizeSelectors = [
          'button:has-text("Authorize")',
          'button:has-text("Allow")',
          'button:has-text("Continue")',
          'button.authorize',
          'button[aria-label="Authorize"]',
          // More generic selectors
          'button.primary',
          'form button',
        ];

        let buttonClicked = false;
        for (const selector of authorizeSelectors) {
          try {
            const button = await page.$(selector);
            if (button && (await button.isVisible())) {
              await button.click();
              console.log(
                `Clicked authorize button with selector: ${selector}`,
              );
              buttonClicked = true;
              await page.waitForNavigation({ timeout: 1000 }).catch((e) => {
                console.log('Navigation after authorize click timed out');
              });
              break;
            }
          } catch (e) {
            console.log(`Error with selector ${selector}:`, e);
          }
        }

        if (!buttonClicked) {
          console.log(
            'No standard authorize button found, trying JavaScript click',
          );
          // Try clicking any button that looks like an authorize button using JavaScript
          await page.evaluate(() => {
            // @ts-ignore
            const buttons = Array.from(document.querySelectorAll('button'));
            const authorizeButton = buttons.find(
              (button: any) =>
                button.innerText.includes('Authorize') ||
                button.innerText.includes('Allow') ||
                button.innerText.includes('Continue'),
            );
            // @ts-ignore
            if (authorizeButton) authorizeButton.click();
          });

          await page.waitForNavigation({ timeout: 1000 }).catch((e) => {
            console.log('Navigation after JS authorize click timed out');
          });
        }
      }
    } catch (error) {
      console.error('Navigation error:', error);
      // Take a screenshot to debug
      await page.screenshot({ path: 'navigation-debug.png' });
      throw error;
    }

    // Wait for the test-complete element to appear, which signals the callback was processed
    console.log('Waiting for OAuth flow to complete...');

    try {
      // Wait for the test-complete element that's added when the callback is successful
      await page.waitForSelector('#test-complete', { timeout: 10000 });
      console.log('Test complete element found, OAuth flow completed!');

      // Take a final screenshot
      await page.screenshot({ path: 'final-callback-page.png' });

      // Close the browser early since we're done
      await browser.close();

      // Verify that we received tokens in the global variable
      expect(oauthTokens).toBeTruthy();
      expect(oauthTokens).toHaveProperty('accessToken');
      expect(oauthTokens).toHaveProperty('refreshToken');

      console.log('OAuth flow completed successfully!');
    } catch (error) {
      console.error('Error waiting for test completion:', error);
      await page.screenshot({ path: 'test-completion-error.png' });
      throw error;
    }
  });
});
