import { Injectable } from '@nestjs/common';
import {
  InvalidClientError,
  InvalidRequestError,
  UnsupportedGrantTypeError,
} from '../../domain/errors';
import type { OAuthCreateTokenInput, OAuthCreateTokenResponse } from '../../domain/flows.types';
import { OAuthCreateTokenAuthorizationCodeService } from './services/o-auth-create-token-authorization-code.service';
import { OAuthCreateTokenPasswordService } from './services/o-auth-create-token-password.service';
import { OAuthCreateTokenRefreshService } from './services/o-auth-create-token-refresh.service';

/**
 * Composite handler for OAuth2 token issuance — one business flow, two transports.
 *
 * Both `OAuthCreateTokenController` (REST `POST /api/o-auth/token`) and
 * `OAuthCreateTokenResolver` (GraphQL `oAuthCreateToken` mutation) parse their
 * on-wire shape into an `OAuthCreateTokenInput` and call `handler.main(input)`. This handler
 * owns:
 *   - Client-credential validation (RFC 6749 §2.3.1) → `invalid_client`.
 *   - Grant-required field validation (RFC 6749 §5.2) → `invalid_request`.
 *   - Unsupported grant rejection (RFC 6749 §5.2) → `unsupported_grant_type`.
 *   - Dispatch between PASSWORD and REFRESH_TOKEN sub-services.
 *
 * The transports MUST NOT replicate ANY of this. They just extract fields from their
 * on-wire format and pass them as-is. See skill `catalyst-handler-composer` →
 * "Composite handlers" section for the convention.
 *
 * Architecture: per Aurora's handler-vs-service split, the handler ORCHESTRATES and
 * VALIDATES; the services PERSIST. Here `OAuthCreateTokenPasswordService` and
 * `OAuthCreateTokenRefreshService` are the variant-specific services that compose the
 * aggregate (access token + refresh token + credential audit row) and write to repos.
 */
@Injectable()
export class OAuthCreateTokenHandler {
  constructor(
    private readonly passwordService: OAuthCreateTokenPasswordService,
    private readonly refreshService: OAuthCreateTokenRefreshService,
    private readonly authCodeService: OAuthCreateTokenAuthorizationCodeService,
  ) {}

  async main(input: OAuthCreateTokenInput): Promise<OAuthCreateTokenResponse> {
    // RFC 6749 §2.3.1 — every grant requires client authentication. Empty clientId or
    // clientSecret indicates the transport could not authenticate the client.
    if (!input.clientId || !input.clientSecret) {
      throw new InvalidClientError();
    }

    if (input.grantType === 'PASSWORD') {
      // RFC 6749 §4.3 — PASSWORD grant requires `username` + `password`.
      const username = input.username?.trim();
      const password = input.password;
      if (!username || !password) throw new InvalidRequestError();

      return this.passwordService.main({
        clientId: input.clientId,
        clientSecret: input.clientSecret,
        username,
        password,
        ipAddress: input.ipAddress,
        userAgent: input.userAgent,
      });
    }

    if (input.grantType === 'REFRESH_TOKEN') {
      // RFC 6749 §6 — REFRESH_TOKEN grant requires the `refresh_token` parameter.
      const refreshToken = input.refreshToken?.trim();
      if (!refreshToken) throw new InvalidRequestError();

      return this.refreshService.main({
        clientId: input.clientId,
        clientSecret: input.clientSecret,
        refreshToken,
        ipAddress: input.ipAddress,
        userAgent: input.userAgent,
      });
    }

    if (input.grantType === 'AUTHORIZATION_CODE') {
      // RFC 6749 §4.1.3 — AUTHORIZATION_CODE grant requires `code`, `redirect_uri`, and `code_verifier`.
      const code = input.code?.trim();
      const redirectUri = input.redirectUri?.trim();
      const codeVerifier = input.codeVerifier?.trim();
      if (!code || !redirectUri || !codeVerifier) throw new InvalidRequestError();

      return this.authCodeService.main({
        clientId: input.clientId,
        clientSecret: input.clientSecret,
        code,
        redirectUri,
        codeVerifier,
        ipAddress: input.ipAddress,
        userAgent: input.userAgent,
      });
    }

    // grantType === 'UNSUPPORTED' — the transport could not map the on-wire grant string
    // to a supported canonical value. RFC 6749 §5.2 → `unsupported_grant_type`.
    throw new UnsupportedGrantTypeError();
  }
}
