import {
  type OAuthCreateTokenInput as OAuthCreateTokenGraphQLInput,
  OAuthCredentialGrantTypeEnum,
  type OAuthTokens,
} from '@api/graphql';
import { OAuthCreateTokenHandler } from '@app/o-auth/flows/application/create-token/o-auth-create-token.handler';
import type {
  OAuthCreateTokenInput,
  OAuthCreateTokenResponse,
} from '@app/o-auth/flows/domain/flows.types';
import { oAuthParseBasicAuth } from '@app/o-auth/shared';
import { GqlThrottlerGuard } from '@aurora/modules/graphql';
import { Injectable, UseGuards } from '@nestjs/common';
import { Args, Context, Mutation, Resolver } from '@nestjs/graphql';
import { Throttle } from '@nestjs/throttler';
import type { Request } from 'express';

const THROTTLE_LIMIT = Number(process.env.THROTTLE_OAUTH_TOKEN_LIMIT ?? 5);
const THROTTLE_TTL_MS = Number(process.env.THROTTLE_OAUTH_TOKEN_TTL_MS ?? 60_000);

/**
 * GraphQL twin of `POST /api/o-auth/token` (mutation: `oAuthCreateToken`). Pure
 * transport layer:
 *   - Decodes HTTP Basic client credentials from the `Authorization` header
 *     (RFC 6749 §2.3.1 — same wire encoding as the REST endpoint).
 *   - Extracts request metadata (ipAddress, userAgent).
 *   - Normalizes the GraphQL enum `OAuthCredentialGrantTypeEnum` to the canonical
 *     `OAuthCreateTokenInput.grantType` discriminator (domain type).
 *   - Maps `OAuthCreateTokenResponse` (snake_case per RFC 6749 §5.1) to the GraphQL
 *     `OAuthTokens` shape (camelCase).
 *
 * ALL business logic — client authentication, grant-field validation, dispatch —
 * lives in `OAuthCreateTokenHandler`. The resolver passes empty/undefined fields and
 * lets the handler raise the RFC 6749 §5.2 errors.
 */
@Injectable()
@Resolver()
export class OAuthCreateTokenResolver {
  constructor(private readonly handler: OAuthCreateTokenHandler) {}

  @Mutation('oAuthCreateToken')
  // RFC 6749 §5.1 — rate-limited at parity with the REST `POST /api/o-auth/token` endpoint
  // (same env-driven budget) so the GraphQL transport is not an unthrottled brute-force vector.
  @UseGuards(GqlThrottlerGuard)
  @Throttle({ default: { limit: THROTTLE_LIMIT, ttl: THROTTLE_TTL_MS } })
  async oAuthCreateToken(
    @Args('payload') payload: OAuthCreateTokenGraphQLInput,
    @Context() context: { req: Request },
  ): Promise<OAuthTokens> {
    const authorization = extractAuthorizationHeader(context.req);
    const { clientId, clientSecret } = oAuthParseBasicAuth(authorization);

    const input: OAuthCreateTokenInput = {
      grantType: normalizeGrantType(payload.grantType),
      rawGrantType: String(payload.grantType),
      clientId,
      clientSecret,
      username: payload.username?.trim() || undefined,
      password: payload.password ?? undefined,
      refreshToken: payload.refreshToken?.trim() || undefined,
      // Authorization Code grant (RFC 6749 §4.1.3 + RFC 7636 PKCE)
      code: payload.code ?? undefined,
      redirectUri: payload.redirectUri ?? undefined,
      codeVerifier: payload.codeVerifier ?? undefined,
      ipAddress: extractIp(context.req),
      userAgent: extractUserAgent(context.req),
    };

    const response = await this.handler.main(input);
    return mapToOAuthTokens(response);
  }
}

function extractAuthorizationHeader(req: Request | undefined): string | undefined {
  const header = req?.headers?.authorization;
  return typeof header === 'string' ? header : undefined;
}

function extractIp(req: Request | undefined): string | null {
  return req?.ip ?? null;
}

function extractUserAgent(req: Request | undefined): string | null {
  const header = req?.headers?.['user-agent'];
  if (typeof header === 'string') return header;
  return null;
}

/**
 * GraphQL enum `OAuthCredentialGrantTypeEnum` → canonical `OAuthCreateTokenInput.grantType`.
 * Pure transport mapping; the supported set is the handler's concern.
 */
function normalizeGrantType(raw: unknown): OAuthCreateTokenInput['grantType'] {
  if (raw === OAuthCredentialGrantTypeEnum.PASSWORD) return 'PASSWORD';
  if (raw === OAuthCredentialGrantTypeEnum.REFRESH_TOKEN) return 'REFRESH_TOKEN';
  if (raw === OAuthCredentialGrantTypeEnum.AUTHORIZATION_CODE) return 'AUTHORIZATION_CODE';
  return 'UNSUPPORTED';
}

function mapToOAuthTokens(response: OAuthCreateTokenResponse): OAuthTokens {
  return {
    accessToken: response.access_token,
    refreshToken: response.refresh_token,
    tokenType: response.token_type,
    expiresIn: response.expires_in,
  };
}
