import {
  BadRequestException,
  Controller,
  Get,
  Headers,
  HttpCode,
  NotFoundException,
  Options,
  Post,
  Query,
  Req,
  Res,
  UnauthorizedException
} from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { firstValueFrom } from 'rxjs';
import type { Request, Response } from 'express';
import { CoreGrpcService } from '../core-grpc.service';
import {
  applyFedcmCorsHeaders,
  applyFedcmLoginStatus,
  applyFedcmPreflightHeaders,
  assertFedcmWebIdentityRequest,
  requireFedcmCorsOrigin
} from '../lib/fedcm-cors';
import {
  buildFedcmDiscoverPayload,
  buildFedcmProviderConfig,
  readOneTapEnabled,
  resolveFedcmEndpoints
} from '../lib/fedcm-config';
import { resolveFedcmSessionFromRequest, setFedcmSessionCookie } from '../lib/fedcm-cookie';
import { verifyAccessToken } from '../session-auth';

type FedcmAccountsResponse = {
  accounts: Array<{
    id: string;
    name: string;
    given_name?: string;
    email?: string;
    picture?: string;
    tel?: string;
    approved_clients?: string[];
  }>;
};

@ApiTags('FedCM')
@Controller('fedcm')
export class FedcmController {
  constructor(
    private readonly core: CoreGrpcService,
    private readonly jwt: JwtService
  ) {}

  @Options('config.json')
  @HttpCode(204)
  configPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
    applyFedcmPreflightHeaders(res, origin);
  }

  @Options('accounts')
  @HttpCode(204)
  accountsPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
    applyFedcmPreflightHeaders(res, origin);
  }

  @Options('id_assertion')
  @HttpCode(204)
  idAssertionPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
    applyFedcmPreflightHeaders(res, origin);
  }

  @Options('client_metadata')
  @HttpCode(204)
  clientMetadataPreflight(@Headers('origin') origin: string | undefined, @Res({ passthrough: true }) res: Response) {
    applyFedcmPreflightHeaders(res, origin);
  }

  @Options('discover.json')
  @HttpCode(204)
  discoverPreflight(@Res({ passthrough: true }) res: Response) {
    res.setHeader('Access-Control-Allow-Origin', '*');
    res.setHeader('Access-Control-Allow-Methods', 'GET, OPTIONS');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Accept');
    res.setHeader('Access-Control-Max-Age', '86400');
  }

  @Get('config.json')
  @ApiOperation({ summary: 'FedCM provider config', description: 'Конфигурация Identity Provider для Federated Credential Management API.' })
  async config(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
    assertFedcmWebIdentityRequest(req);
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=300');

    const enabled = await readOneTapEnabled(this.core);
    if (!enabled) {
      throw new NotFoundException('One Tap Login отключён');
    }

    const endpoints = await resolveFedcmEndpoints(this.core, req);
    return buildFedcmProviderConfig(endpoints);
  }

  @Get('discover.json')
  @ApiOperation({
    summary: 'FedCM discovery',
    description: 'Публичные URL FedCM для виджета и диагностики (apiBase, configUrl, webIdentityUrl).'
  })
  async discover(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Cache-Control', 'public, max-age=60');
    res.setHeader('Access-Control-Allow-Origin', '*');

    const enabled = await readOneTapEnabled(this.core);
    const endpoints = await resolveFedcmEndpoints(this.core, req);
    return buildFedcmDiscoverPayload(endpoints, enabled);
  }

  @Get('accounts')
  @ApiOperation({ summary: 'FedCM accounts', description: 'Возвращает аккаунты пользователя по FedCM-сессии (cookie).' })
  async accounts(
    @Req() req: Request,
    @Headers('origin') origin: string | undefined,
    @Res({ passthrough: true }) res: Response
  ): Promise<FedcmAccountsResponse> {
    assertFedcmWebIdentityRequest(req);
    applyFedcmCorsHeaders(res, origin);
    res.setHeader('Content-Type', 'application/json; charset=utf-8');

    const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
    if (!session) {
      applyFedcmLoginStatus(res, false);
      return { accounts: [] };
    }

    try {
      const result = (await firstValueFrom(
        this.core.fedcm.GetAccounts({ userId: session.sub, sessionId: session.sessionId })
      )) as FedcmAccountsResponse;
      const accounts = result?.accounts ?? [];
      applyFedcmLoginStatus(res, accounts.length > 0);
      return { accounts };
    } catch {
      applyFedcmLoginStatus(res, false);
      return { accounts: [] };
    }
  }

  @Get('client_metadata')
  @ApiOperation({ summary: 'FedCM client metadata', description: 'Метаданные клиента для UI FedCM.' })
  async clientMetadata(
    @Req() req: Request,
    @Query('client_id') clientId: string | undefined,
    @Headers('origin') origin: string | undefined,
    @Res({ passthrough: true }) res: Response
  ) {
    assertFedcmWebIdentityRequest(req);
    const rpOrigin = requireFedcmCorsOrigin(origin);
    applyFedcmCorsHeaders(res, rpOrigin);
    res.setHeader('Content-Type', 'application/json; charset=utf-8');

    if (!clientId?.trim()) {
      throw new BadRequestException('Передайте client_id');
    }

    return firstValueFrom(this.core.fedcm.GetClientMetadata({ clientId: clientId.trim() }));
  }

  @Post('id_assertion')
  @HttpCode(200)
  @ApiOperation({ summary: 'FedCM id assertion', description: 'Выдаёт OIDC id_token для выбранного аккаунта и OAuth-клиента.' })
  async idAssertion(
    @Req() req: Request,
    @Headers('origin') origin: string | undefined,
    @Res({ passthrough: true }) res: Response
  ) {
    assertFedcmWebIdentityRequest(req);
    const rpOrigin = requireFedcmCorsOrigin(origin);
    applyFedcmCorsHeaders(res, rpOrigin);
    res.setHeader('Content-Type', 'application/json; charset=utf-8');

    const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
    if (!session) {
      throw new UnauthorizedException('Сессия FedCM не найдена');
    }

    const body = (req.body ?? {}) as Record<string, unknown>;
    const clientId = String(body.client_id ?? body.clientId ?? '').trim();
    const accountId = String(body.account_id ?? body.accountId ?? '').trim();
    if (!clientId || !accountId) {
      throw new BadRequestException('Передайте client_id и account_id');
    }

    applyFedcmLoginStatus(res, true);

    return firstValueFrom(
      this.core.fedcm.IssueIdAssertion({
        userId: session.sub,
        sessionId: session.sessionId,
        clientId,
        accountId
      })
    );
  }

  private async syncFedcmSessionFromAuthorization(
    authorization: string | undefined,
    res: Response
  ) {
    const payload = await verifyAccessToken(this.jwt, authorization);
    if (!payload.sessionId) {
      throw new UnauthorizedException('Сессия не найдена');
    }

    const validation = (await firstValueFrom(
      this.core.auth.ValidateSession({
        userId: payload.sub,
        sessionId: payload.sessionId,
        touchActivity: false
      })
    )) as { requiresPin: boolean; sessionId: string; pinVerified: boolean };

    await setFedcmSessionCookie(res, this.jwt, {
      sub: payload.sub,
      sessionId: payload.sessionId,
      pinVerified: !validation.requiresPin
    });
    applyFedcmLoginStatus(res, true);
    return { synced: true, requiresPin: validation.requiresPin };
  }

  @Post('session/sync')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Синхронизировать FedCM cookie',
    description: 'Устанавливает HttpOnly cookie для FedCM по Bearer access token (для уже авторизованных пользователей IdP).'
  })
  syncSessionPost(
    @Headers('authorization') authorization: string | undefined,
    @Res({ passthrough: true }) res: Response
  ) {
    return this.syncFedcmSessionFromAuthorization(authorization, res);
  }

  @Get('session/sync')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Синхронизировать FedCM cookie (GET)',
    description: 'Тот же sync по Bearer token. GET нужен для совместимости с редиректами прокси и prefetch.'
  })
  syncSessionGet(
    @Headers('authorization') authorization: string | undefined,
    @Res({ passthrough: true }) res: Response
  ) {
    return this.syncFedcmSessionFromAuthorization(authorization, res);
  }

  @Get('session/status')
  @HttpCode(200)
  @ApiOperation({
    summary: 'Состояние FedCM-сессии',
    description: 'Для login_url на API-домене: проверяет cookie и PIN-блокировку без Bearer token.'
  })
  async sessionStatus(@Req() req: Request, @Res({ passthrough: true }) res: Response) {
    res.setHeader('Content-Type', 'application/json; charset=utf-8');
    res.setHeader('Cache-Control', 'no-store');

    const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
    if (!session) {
      applyFedcmLoginStatus(res, false);
      return { active: false, requiresPin: false, sessionId: null, userId: null };
    }

    const validation = (await firstValueFrom(
      this.core.auth.ValidateSession({
        userId: session.sub,
        sessionId: session.sessionId,
        touchActivity: false
      })
    )) as { requiresPin: boolean; sessionId: string; pinVerified: boolean };

    const requiresPin = validation.requiresPin || !session.pinVerified;
    applyFedcmLoginStatus(res, true);

    return {
      active: true,
      requiresPin,
      sessionId: validation.sessionId,
      userId: session.sub
    };
  }

  @Get('login-status')
  @ApiOperation({
    summary: 'FedCM Login Status (API origin)',
    description:
      'Минимальная HTML-страница на API-домене: Set-Login + navigator.login.setStatus для Chrome FedCM (origin login_url).'
  })
  async loginStatus(@Req() req: Request, @Res() res: Response) {
    const session = await resolveFedcmSessionFromRequest(this.jwt, req.headers.cookie);
    const loggedIn = Boolean(session);
    applyFedcmLoginStatus(res, loggedIn);
    res.setHeader('Content-Type', 'text/html; charset=utf-8');
    res.setHeader('Cache-Control', 'no-store');
    res.status(200).send(`<!DOCTYPE html><html lang="ru"><head><meta charset="utf-8"></head><body><script>
try{if(navigator.login&&navigator.login.setStatus){navigator.login.setStatus('${loggedIn ? 'logged-in' : 'logged-out'}');}}catch(e){}
</script></body></html>`);
  }
}
