import * as fs from 'fs';
import type { BrowserContext, Page } from 'playwright';

import type { AuthenticationServicePort } from '../../../../application/ports/AuthenticationServicePort';
import type { LoggerPort } from '../../../../application/ports/LoggerPort';
import { AuthenticationState } from '../../../../domain/value-objects/AuthenticationState';
import { BrowserAuthenticationState } from '../../../../domain/value-objects/BrowserAuthenticationState';
import { Result } from '@gridpilot/shared/application/Result';
import { PlaywrightBrowserSession } from '../core/PlaywrightBrowserSession';
import { SessionCookieStore } from './SessionCookieStore';
import type { IPlaywrightAuthFlow } from './PlaywrightAuthFlow';
import { AuthenticationGuard } from './AuthenticationGuard';

interface PlaywrightAuthSessionConfig {
  navigationTimeoutMs?: number;
  loginWaitTimeoutMs?: number;
}

/**
 * Game-agnostic Playwright-based authentication/session service.
 *
 * All game/site-specific behavior (URLs, selectors, redirects) is delegated to
 * the injected IPlaywrightAuthFlow implementation. This class is responsible
 * only for:
 * - Browser/session orchestration via PlaywrightBrowserSession
 * - Cookie persistence via SessionCookieStore
 * - Exposing the IAuthenticationService port for application layer
 */
export class PlaywrightAuthSessionService implements AuthenticationServicePort {
private readonly browserSession: PlaywrightBrowserSession;
private readonly cookieStore: SessionCookieStore;
private readonly authFlow: IPlaywrightAuthFlow;
private readonly logger: LoggerPort | undefined;

  private readonly navigationTimeoutMs: number;
  private readonly loginWaitTimeoutMs: number;

  private authState: AuthenticationState = AuthenticationState.UNKNOWN;

  constructor(
    browserSession: PlaywrightBrowserSession,
    cookieStore: SessionCookieStore,
    authFlow: IPlaywrightAuthFlow,
    logger?: LoggerPort,
    config?: PlaywrightAuthSessionConfig,
  ) {
    this.browserSession = browserSession;
    this.cookieStore = cookieStore;
    this.authFlow = authFlow;
    this.logger = logger;

    this.navigationTimeoutMs = config?.navigationTimeoutMs ?? 30000;
    this.loginWaitTimeoutMs = config?.loginWaitTimeoutMs ?? 300000;
  }

  // ===== Logging =====

  private log(
    level: 'debug' | 'info' | 'warn' | 'error',
    message: string,
    context?: Record<string, unknown>,
  ): void {
    if (!this.logger) {
      return;
    }
    const logger = this.logger as Record<
      'debug' | 'info' | 'warn' | 'error',
      (msg: string, ctx?: Record<string, unknown>) => void
    >;
    logger[level](message, context);
  }

  // ===== Helpers =====

  private getContext(): BrowserContext | null {
    return this.browserSession.getPersistentContext() ?? this.browserSession.getContext();
  }

  private getPageOrError(): Result<Page> {
    const page = this.browserSession.getPage();
    if (!page) {
      return Result.err(new Error('Browser not connected'));
    }
    return Result.ok(page);
  }

  private async injectCookiesBeforeNavigation(targetUrl: string): Promise<Result<void>> {
    const context = this.getContext();
    if (!context) {
      return Result.err(new Error('No browser context available'));
    }

    try {
      const state = await this.cookieStore.read();
      if (!state || state.cookies.length === 0) {
        return Result.err(new Error('No cookies found in session store'));
      }

      const validCookies = this.cookieStore.getValidCookiesForUrl(targetUrl);
      if (validCookies.length === 0) {
        this.log('warn', 'No valid cookies found for target URL', {
          targetUrl,
          totalCookies: state.cookies.length,
        });
        return Result.err(new Error('No valid cookies found for target URL'));
      }

      await context.addCookies(validCookies);

      this.log('info', 'Cookies injected successfully', {
        count: validCookies.length,
        targetUrl,
        cookieNames: validCookies.map((c) => c.name),
      });

      return Result.ok(undefined);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return Result.err(new Error(`Cookie injection failed: ${message}`));
    }
  }

  private async saveSessionState(): Promise<void> {
    const context = this.getContext();
    if (!context) {
      this.log('warn', 'No browser context available to save session state');
      return;
    }

    try {
      const storageState = await context.storageState();
      await this.cookieStore.write(storageState);
      this.log('info', 'Session state saved to cookie store');
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to save session state', { error: message });
      throw error;
    }
  }

  // ===== IAuthenticationService implementation =====

  async checkSession(): Promise<Result<AuthenticationState>> {
    try {
      this.log('info', 'Checking session from cookie store');

      const state = await this.cookieStore.read();
      if (!state) {
        this.authState = AuthenticationState.UNKNOWN;
        this.log('info', 'No session state file found');
        return Result.ok(this.authState);
      }

      this.authState = this.cookieStore.validateCookies(state.cookies);
      this.log('info', 'Session check complete', { state: this.authState });
      return Result.ok(this.authState);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Session check failed', { error: message });
      return Result.err(new Error(`Session check failed: ${message}`));
    }
  }

  getLoginUrl(): string {
    return this.authFlow.getLoginUrl();
  }

  async initiateLogin(): Promise<Result<void>> {
    try {
      const forceHeaded = true;
      this.log('info', 'Opening login in headed Playwright browser (forceHeaded=true)', {
        forceHeaded,
      });

      const connectResult = await this.browserSession.connect(forceHeaded);
      if (!connectResult.success) {
        return Result.err(new Error(connectResult.error || 'Failed to connect browser'));
      }

      const pageResult = this.getPageOrError();
      if (pageResult.isErr()) {
        return Result.err(pageResult.unwrapErr());
      }
      const page = pageResult.unwrap();

      const loginUrl = this.authFlow.getLoginUrl();
      await page.goto(loginUrl, {
        waitUntil: 'domcontentloaded',
        timeout: this.navigationTimeoutMs,
      });

      this.log('info', forceHeaded
        ? 'Browser opened to login page in headed mode, waiting for login...'
        : 'Browser opened to login page, waiting for login...');
      this.authState = AuthenticationState.UNKNOWN;

      const loginSuccess = await this.authFlow.waitForPostLoginRedirect(
        page,
        this.loginWaitTimeoutMs,
      );

      if (loginSuccess) {
        this.log('info', 'Login detected, saving session state');
        await this.saveSessionState();

        const state = await this.cookieStore.read();
        if (state && this.cookieStore.validateCookies(state.cookies) === AuthenticationState.AUTHENTICATED) {
          this.authState = AuthenticationState.AUTHENTICATED;
          this.log('info', 'Session saved and validated successfully');
        } else {
          this.authState = AuthenticationState.UNKNOWN;
          this.log('warn', 'Session saved but validation unclear');
        }

        this.log('info', 'Closing browser after successful login');
        await this.browserSession.disconnect();
        return Result.ok(undefined);
      }

      this.log('warn', 'Login was not completed');
      await this.browserSession.disconnect();
      return Result.err(new Error('Login timeout - please try again'));
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed during login process', { error: message });

      try {
        await this.browserSession.disconnect();
      } catch {
      }

      return Result.err(error instanceof Error ? error : new Error(message));
    }
  }

  async confirmLoginComplete(): Promise<Result<void>> {
    try {
      this.log('info', 'User confirmed login complete');

      await this.saveSessionState();

      const state = await this.cookieStore.read();
      if (state && this.cookieStore.validateCookies(state.cookies) === AuthenticationState.AUTHENTICATED) {
        this.authState = AuthenticationState.AUTHENTICATED;
        this.log('info', 'Login confirmed and session saved successfully');
      } else {
        this.authState = AuthenticationState.UNKNOWN;
        this.log('warn', 'Login confirmation received but session state unclear');
      }

      return Result.ok(undefined);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to confirm login', { error: message });
      return Result.err(error instanceof Error ? error : new Error(message));
    }
  }

  async clearSession(): Promise<Result<void>> {
    try {
      this.log('info', 'Clearing session');

      await this.cookieStore.delete();
      this.log('debug', 'Cookie store deleted');

      const userDataDir = this.browserSession.getUserDataDir();
      if (userDataDir && fs.existsSync(userDataDir)) {
        this.log('debug', 'Removing user data directory', { path: userDataDir });
        fs.rmSync(userDataDir, { recursive: true, force: true });
      }

      this.authState = AuthenticationState.LOGGED_OUT;
      this.log('info', 'Session cleared successfully');
      return Result.ok(undefined);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to clear session', { error: message });
      return Result.err(new Error(`Failed to clear session: ${message}`));
    }
  }

  getState(): AuthenticationState {
    return this.authState;
  }

  async validateServerSide(): Promise<Result<boolean>> {
    try {
      this.log('info', 'Performing server-side session validation');

      const context = this.getContext();
      if (!context) {
        return Result.err(new Error('No browser context available'));
      }

      const page = await context.newPage();

      try {
        const response = await page.goto(this.authFlow.getPostLoginLandingUrl(), {
          waitUntil: 'domcontentloaded',
          timeout: this.navigationTimeoutMs,
        });

        if (!response) {
          return Result.ok(false);
        }

        const finalUrl = page.url();
        const isOnLoginPage = this.authFlow.isLoginUrl(finalUrl);

        const isValid = !isOnLoginPage;
        this.log('info', 'Server-side validation complete', { isValid, finalUrl });

        return Result.ok(isValid);
      } finally {
        await page.close();
      }
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('warn', 'Server-side validation failed', { error: message });
      return Result.err(new Error(`Server validation failed: ${message}`));
    }
  }

  async refreshSession(): Promise<Result<void>> {
    try {
      this.log('info', 'Refreshing session from cookie store');

      const state = await this.cookieStore.read();
      if (!state) {
        this.authState = AuthenticationState.UNKNOWN;
        return Result.ok(undefined);
      }

      this.authState = this.cookieStore.validateCookies(state.cookies);
      this.log('info', 'Session refreshed', { state: this.authState });

      return Result.ok(undefined);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Session refresh failed', { error: message });
      return Result.err(new Error(`Session refresh failed: ${message}`));
    }
  }

  async getSessionExpiry(): Promise<Result<Date | null>> {
    try {
      const expiry = await this.cookieStore.getSessionExpiry();
      return Result.ok(expiry);
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to get session expiry', { error: message });
      return Result.err(new Error(`Failed to get session expiry: ${message}`));
    }
  }

  async verifyPageAuthentication(): Promise<Result<BrowserAuthenticationState>> {
    const pageResult = this.getPageOrError();
    if (pageResult.isErr()) {
      return Result.err(pageResult.unwrapErr());
    }
    const page = pageResult.unwrap();

    try {
      const url = page.url();

      const isOnAuthenticatedPath = this.authFlow.isAuthenticatedUrl(url);
      const isOnLoginPath = this.authFlow.isLoginUrl(url);

      const guard = new AuthenticationGuard(page, this.logger);
      const hasLoginUI = await guard.checkForLoginUI();

      const hasAuthUI = await this.authFlow.detectAuthenticatedUi(page);

      const cookieResult = await this.checkSession();
      const cookiesValid =
        cookieResult.isOk() &&
        cookieResult.unwrap() === AuthenticationState.AUTHENTICATED;

      const pageAuthenticated =
        !hasLoginUI &&
        !isOnLoginPath &&
        ((isOnAuthenticatedPath && cookiesValid) || hasAuthUI);

      this.log('debug', 'Page authentication check', {
        url,
        isOnAuthenticatedPath,
        isOnLoginPath,
        hasLoginUI,
        hasAuthUI,
        cookiesValid,
        pageAuthenticated,
      });

      return Result.ok(new BrowserAuthenticationState(cookiesValid, pageAuthenticated));
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      return Result.err(new Error(`Page verification failed: ${message}`));
    }
  }

  // ===== Public helper for navigation with cookie injection =====

  /**
   * Navigate to an authenticated area using stored cookies when possible.
   * Not part of the IAuthenticationService port, but useful for internal
   * orchestration (e.g. within automation flows).
   */
  async navigateWithExistingSession(forceHeaded: boolean = false): Promise<Result<void>> {
    try {
      const sessionResult = await this.checkSession();
      if (
        sessionResult.isOk() &&
        sessionResult.unwrap() === AuthenticationState.AUTHENTICATED
      ) {
        this.log('info', 'Session cookies found, launching in configured browser mode');

        await this.browserSession.ensureBrowserContext(forceHeaded);
        const pageResult = this.getPageOrError();
        if (pageResult.isErr()) {
          return Result.err(pageResult.unwrapErr());
        }
        const page = pageResult.unwrap();

        const targetUrl = this.authFlow.getPostLoginLandingUrl();
        const injectResult = await this.injectCookiesBeforeNavigation(targetUrl);

        if (injectResult.isErr()) {
          this.log('warn', 'Cookie injection failed, falling back to manual login', {
            error: injectResult.error?.message ?? 'unknown error',
          });
          return Result.err(injectResult.unwrapErr());
        }

        await page.goto(targetUrl, {
          waitUntil: 'domcontentloaded',
          timeout: this.navigationTimeoutMs,
        });

        const verifyResult = await this.verifyPageAuthentication();
        if (verifyResult.isOk()) {
          const browserState = verifyResult.unwrap();
          if (browserState.isFullyAuthenticated()) {
            this.log('info', 'Authentication verified successfully after cookie navigation');
            return Result.ok(undefined);
          }
          this.log('warn', 'Page shows unauthenticated state despite cookies');
        }

        return Result.err(new Error('Page not authenticated after cookie navigation'));
      }

      return Result.err(new Error('No valid session cookies found'));
    } catch (error) {
      const message = error instanceof Error ? error.message : String(error);
      this.log('error', 'Failed to navigate with existing session', { error: message });
      return Result.err(new Error(`Failed to navigate with existing session: ${message}`));
    }
  }
}