export interface TotpProfile {
  id: string;
  issuer: string;
  label: string;
  secret: string;
  digits: 6 | 8;
  period: number;
  createdAt: string;
}

const BASE32_ALPHABET = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';

export function normalizeBase32Secret(secret: string) {
  return secret.replace(/[\s-]/g, '').toUpperCase();
}

export function parseOtpAuthUrl(value: string): Partial<TotpProfile> | null {
  try {
    const url = new URL(value);
    if (url.protocol !== 'otpauth:' || url.hostname !== 'totp') return null;
    const labelRaw = decodeURIComponent(url.pathname.replace(/^\//, ''));
    const [issuerFromLabel, accountFromLabel] = labelRaw.includes(':') ? labelRaw.split(/:(.*)/s) : ['', labelRaw];
    const secret = url.searchParams.get('secret');
    if (!secret) return null;
    const digits = Number(url.searchParams.get('digits') ?? 6);
    const period = Number(url.searchParams.get('period') ?? 30);
    return {
      issuer: url.searchParams.get('issuer') ?? issuerFromLabel ?? '',
      label: accountFromLabel || labelRaw,
      secret: normalizeBase32Secret(secret),
      digits: digits === 8 ? 8 : 6,
      period: Number.isFinite(period) && period > 0 ? period : 30
    };
  } catch {
    return null;
  }
}

export function decodeBase32(secret: string) {
  const normalized = normalizeBase32Secret(secret).replace(/=+$/g, '');
  let bits = '';
  for (const char of normalized) {
    const value = BASE32_ALPHABET.indexOf(char);
    if (value === -1) {
      throw new Error('Некорректный base32 secret');
    }
    bits += value.toString(2).padStart(5, '0');
  }

  const bytes: number[] = [];
  for (let index = 0; index + 8 <= bits.length; index += 8) {
    bytes.push(Number.parseInt(bits.slice(index, index + 8), 2));
  }
  return new Uint8Array(bytes);
}

function counterToBytes(counter: number) {
  const bytes = new ArrayBuffer(8);
  const view = new DataView(bytes);
  view.setUint32(4, counter, false);
  return bytes;
}

export async function generateTotpCode(profile: Pick<TotpProfile, 'secret' | 'digits' | 'period'>, now = Date.now()) {
  const keyData = decodeBase32(profile.secret);
  const key = await crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-1' }, false, ['sign']);
  const counter = Math.floor(now / 1000 / profile.period);
  const signature = new Uint8Array(await crypto.subtle.sign('HMAC', key, counterToBytes(counter)));
  const offset = signature[signature.length - 1]! & 0x0f;
  const binary =
    ((signature[offset]! & 0x7f) << 24) |
    ((signature[offset + 1]! & 0xff) << 16) |
    ((signature[offset + 2]! & 0xff) << 8) |
    (signature[offset + 3]! & 0xff);
  const mod = 10 ** profile.digits;
  return String(binary % mod).padStart(profile.digits, '0');
}

export function getRemainingSeconds(period: number, now = Date.now()) {
  return period - (Math.floor(now / 1000) % period);
}
