import { BadRequestException } from '@nestjs/common';

export interface NormalizedAuthorizeQuery {
  userId?: string;
  clientId: string;
  redirectUri: string;
  scope: string;
  state?: string;
  responseType?: string;
  codeChallenge?: string;
  codeChallengeMethod?: string;
  nonce?: string;
}

export interface NormalizedTokenBody {
  grantType: string;
  code?: string;
  refreshToken?: string;
  clientId: string;
  clientSecret?: string;
  redirectUri?: string;
  codeVerifier?: string;
}

function readString(value: unknown) {
  if (typeof value === 'string') return value.trim();
  if (Array.isArray(value) && typeof value[0] === 'string') return value[0].trim();
  return undefined;
}

export function normalizeAuthorizeQuery(query: Record<string, unknown>): NormalizedAuthorizeQuery {
  const clientId = readString(query.clientId) ?? readString(query.client_id);
  const redirectUri = readString(query.redirectUri) ?? readString(query.redirect_uri);
  const scope = readString(query.scope) ?? 'openid profile';
  const userId = readString(query.userId) ?? readString(query.user_id);
  const state = readString(query.state);
  const responseType = readString(query.response_type) ?? readString(query.responseType);
  const codeChallenge = readString(query.code_challenge) ?? readString(query.codeChallenge);
  const codeChallengeMethod = readString(query.code_challenge_method) ?? readString(query.codeChallengeMethod);
  const nonce = readString(query.nonce);

  if (!clientId) {
    throw new BadRequestException('Укажите client_id');
  }
  if (!redirectUri) {
    throw new BadRequestException('Укажите redirect_uri');
  }
  if (responseType && responseType !== 'code') {
    throw new BadRequestException('Поддерживается только response_type=code');
  }

  return {
    userId,
    clientId,
    redirectUri,
    scope,
    state,
    responseType,
    codeChallenge,
    codeChallengeMethod,
    nonce
  };
}

export function normalizeConsentQuery(query: Record<string, unknown>) {
  const clientId = readString(query.clientId) ?? readString(query.client_id);
  const scope = readString(query.scope) ?? 'openid profile';
  const redirectUri = readString(query.redirectUri) ?? readString(query.redirect_uri);
  const state = readString(query.state);
  const nonce = readString(query.nonce);

  if (!clientId) {
    throw new BadRequestException('Укажите client_id');
  }

  return { clientId, scope, redirectUri, state, nonce };
}

export function normalizeTokenBody(body: Record<string, unknown>): NormalizedTokenBody {
  const grantType = readString(body.grantType) ?? readString(body.grant_type);
  const clientId = readString(body.clientId) ?? readString(body.client_id);
  const clientSecret = readString(body.clientSecret) ?? readString(body.client_secret);
  const code = readString(body.code);
  const refreshToken = readString(body.refreshToken) ?? readString(body.refresh_token);
  const redirectUri = readString(body.redirectUri) ?? readString(body.redirect_uri);
  const codeVerifier = readString(body.codeVerifier) ?? readString(body.code_verifier);

  if (!grantType) {
    throw new BadRequestException('Укажите grant_type');
  }
  if (!clientId) {
    throw new BadRequestException('Укажите client_id');
  }

  return {
    grantType,
    clientId,
    clientSecret,
    code,
    refreshToken,
    redirectUri,
    codeVerifier
  };
}

export function appendQueryParams(baseUrl: string, query: Record<string, unknown>) {
  const url = new URL(baseUrl);
  for (const [key, value] of Object.entries(query)) {
    if (value === undefined || value === null || value === '') continue;
    if (Array.isArray(value)) {
      value.forEach((item) => url.searchParams.append(key, String(item)));
      continue;
    }
    url.searchParams.set(key, String(value));
  }
  return url.toString();
}

export function parseBasicClientCredentials(authorization?: string) {
  if (!authorization?.startsWith('Basic ')) return null;
  try {
    const decoded = Buffer.from(authorization.slice(6), 'base64').toString('utf8');
    const separator = decoded.indexOf(':');
    if (separator < 0) return null;
    return {
      clientId: decoded.slice(0, separator),
      clientSecret: decoded.slice(separator + 1)
    };
  } catch {
    return null;
  }
}

export function mapTokenResponseToStandard(result: {
  accessToken?: string;
  tokenType?: string;
  expiresIn?: number;
  refreshToken?: string;
  idToken?: string;
}) {
  return {
    access_token: result.accessToken,
    token_type: result.tokenType ?? 'Bearer',
    expires_in: result.expiresIn ?? 900,
    refresh_token: result.refreshToken,
    id_token: result.idToken
  };
}

export function mapUserInfoToOidc(result: {
  sub?: string;
  email?: string;
  phone?: string;
  name?: string;
  picture?: string;
  emailVerified?: boolean;
  preferredUsername?: string;
  phoneNumber?: string;
  phoneNumberVerified?: boolean;
}) {
  const claims: Record<string, unknown> = { sub: result.sub };
  if (result.name) claims.name = result.name;
  if (result.picture) claims.picture = result.picture;
  if (result.preferredUsername) claims.preferred_username = result.preferredUsername;
  if (result.email) {
    claims.email = result.email;
    if (result.emailVerified !== undefined) claims.email_verified = result.emailVerified;
  }
  const phoneNumber = result.phoneNumber ?? result.phone;
  if (phoneNumber) {
    claims.phone_number = phoneNumber;
    if (result.phoneNumberVerified !== undefined) claims.phone_number_verified = result.phoneNumberVerified;
  }
  return claims;
}

export function mergeTokenCredentials(
  body: NormalizedTokenBody,
  authorization?: string
): NormalizedTokenBody {
  const basic = parseBasicClientCredentials(authorization);
  if (!basic) return body;
  return {
    ...body,
    clientId: body.clientId || basic.clientId,
    clientSecret: body.clientSecret || basic.clientSecret
  };
}

function readRecord(value: unknown) {
  return value && typeof value === 'object' ? (value as Record<string, unknown>) : null;
}

export function mapOAuthClientPublicInfo(raw: Record<string, unknown>) {
  return {
    clientId: String(raw.clientId ?? raw.client_id ?? ''),
    name: String(raw.name ?? '')
  };
}

export function mapOAuthScopeInfo(raw: Record<string, unknown>) {
  const slug = String(raw.slug ?? '');
  return {
    slug,
    name: String(raw.name ?? slug),
    description: raw.description ? String(raw.description) : undefined
  };
}

export function mapOAuthConsentCheckResponse(raw: Record<string, unknown>) {
  const clientRaw = readRecord(raw.client);
  const scopesRaw = Array.isArray(raw.requestedScopes)
    ? raw.requestedScopes
    : Array.isArray(raw.requested_scopes)
      ? raw.requested_scopes
      : [];

  return {
    granted: Boolean(raw.granted),
    client: clientRaw
      ? mapOAuthClientPublicInfo(clientRaw)
      : undefined,
    requestedScopes: scopesRaw
      .map((item) => readRecord(item))
      .filter((item): item is Record<string, unknown> => Boolean(item))
      .map(mapOAuthScopeInfo)
  };
}

export function mapUserOAuthConsentsResponse(raw: Record<string, unknown>) {
  const consentsRaw = Array.isArray(raw.consents) ? raw.consents : [];
  return {
    consents: consentsRaw
      .map((item) => readRecord(item))
      .filter((item): item is Record<string, unknown> => Boolean(item))
      .map((consent) => ({
        id: String(consent.id ?? ''),
        clientId: String(consent.clientId ?? consent.client_id ?? ''),
        clientName: String(consent.clientName ?? consent.client_name ?? consent.clientId ?? consent.client_id ?? ''),
        scopes: (Array.isArray(consent.scopes) ? consent.scopes : [])
          .map((item) => readRecord(item))
          .filter((item): item is Record<string, unknown> => Boolean(item))
          .map(mapOAuthScopeInfo),
        grantedAt: String(consent.grantedAt ?? consent.granted_at ?? ''),
        updatedAt: String(consent.updatedAt ?? consent.updated_at ?? '')
      }))
  };
}
