#!/usr/bin/env tsx
/**
 * E2E: email OTP registration → portal phone verify → docs → verified
 * Also covers Google-skip path is not exercised here (needs real Google token).
 */
import fs from 'fs';
import path from 'path';
import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
const API_BASE = (process.env.API_URL || 'http://localhost:4000').replace(/\/$/, '');
const API = API_BASE.endsWith('/api') ? API_BASE : `${API_BASE}/api`;
const PASS = process.env.DEMO_PASSWORD || 'Password123!';

let passed = 0;
let failed = 0;

function pass(label: string) {
  console.log(`  ✓ ${label}`);
  passed += 1;
}

function fail(label: string, detail?: string) {
  console.log(`  ✗ ${label}${detail ? `: ${detail}` : ''}`);
  failed += 1;
}

async function login(identifier: string) {
  const res = await fetch(`${API}/auth/login`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ identifier, password: PASS }),
  });
  const data = await res.json();
  if (!data.success) throw new Error(data.error || `login failed for ${identifier}`);
  return data.data.accessToken as string;
}

async function api<T>(
  token: string | null,
  method: string,
  endpoint: string,
  body?: unknown
): Promise<T> {
  const res = await fetch(`${API}${endpoint}`, {
    method,
    headers: {
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...(body ? { 'Content-Type': 'application/json' } : {}),
    },
    body: body ? JSON.stringify(body) : undefined,
  });
  const data = await res.json();
  return data as T;
}

function createTestPng(filePath: string) {
  const png = Buffer.from(
    'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==',
    'base64'
  );
  fs.writeFileSync(filePath, png);
}

async function uploadDoc(token: string, type: string, filePath: string) {
  const form = new FormData();
  const blob = new Blob([fs.readFileSync(filePath)], { type: 'image/png' });
  form.append('document', blob, path.basename(filePath));
  form.append('type', type);
  const res = await fetch(`${API}/verifications`, {
    method: 'POST',
    headers: { Authorization: `Bearer ${token}` },
    body: form,
  });
  const data = await res.json();
  if (!data.success) throw new Error(data.error || 'upload failed');
  return data.data as { id: string };
}

async function registerWorkerViaEmailOtp(opts: {
  email: string;
  firstName: string;
  lastName: string;
  displayName: string;
  categoryId: string;
}) {
  const send = await api<{ success: boolean; error?: string }>(null, 'POST', '/auth/worker/send-otp', {
    email: opts.email,
  });
  if (!send.success) throw new Error(send.error || 'send-otp failed');

  const otpRow = await prisma.oTPVerification.findFirst({
    where: { phone: opts.email.toLowerCase(), purpose: 'worker_register', isUsed: false },
    orderBy: { createdAt: 'desc' },
  });
  if (!otpRow?.code) throw new Error('OTP not found for email');

  const reg = await api<{
    success: boolean;
    error?: string;
    data: { accessToken: string; user: { id: string } };
  }>(null, 'POST', '/auth/register', {
    firstName: opts.firstName,
    lastName: opts.lastName,
    displayName: opts.displayName,
    email: opts.email,
    password: PASS,
    role: 'WORKER',
    gender: 'MALE',
    otpCode: otpRow.code,
    serviceAreas: ['DAVAO_CITY'],
    categoryIds: [opts.categoryId],
  });
  if (!reg.success) throw new Error(reg.error || JSON.stringify(reg));
  return reg.data.accessToken;
}

async function main() {
  console.log('=== Email OTP registration + phone portal verification E2E ===\n');

  const stamp = Date.now();
  const email = `email.otp.${stamp}@ehandyhire.test`;
  const phone = `+63917${String(stamp).slice(-7)}`;
  const tmpPng = path.join(process.cwd(), 'uploads', 'test-id.png');
  fs.mkdirSync(path.dirname(tmpPng), { recursive: true });
  createTestPng(tmpPng);

  const catRes = await api<{ success: boolean; data: { id: string }[] }>(
    null,
    'GET',
    '/categories?limit=1'
  );
  if (!catRes.data?.[0]?.id) throw new Error('No categories — is API running?');
  const categoryId = catRes.data[0].id;

  // 1. Register with email OTP (no phone)
  const workerToken = await registerWorkerViaEmailOtp({
    email,
    firstName: 'Email',
    lastName: 'Otp',
    displayName: 'Mang Email',
    categoryId,
  });
  pass(`Registered worker via email OTP (${email})`);

  const me1 = await api<{
    success: boolean;
    data: {
      email: string;
      phone: string | null;
      phoneVerified: boolean;
      emailVerified: boolean;
      workerProfile: { id: string; isVerified: boolean };
    };
  }>(workerToken, 'GET', '/auth/me');

  if (me1.data.emailVerified && !me1.data.phoneVerified && !me1.data.phone) {
    pass('New worker: email verified, phone not set yet');
  } else {
    fail(
      'Expected emailVerified + no phone',
      JSON.stringify({
        emailVerified: me1.data.emailVerified,
        phoneVerified: me1.data.phoneVerified,
        phone: me1.data.phone,
      })
    );
  }
  if (!me1.data.workerProfile.isVerified) pass('Worker starts unverified');
  else fail('Worker should start unverified');

  const workerId = me1.data.workerProfile.id;
  const adminToken = await login('admin@ehandyhire.com');
  pass('Admin login');

  // 2. Upload + approve docs BEFORE phone — must NOT verify yet
  const idDoc = await uploadDoc(workerToken, 'GOVERNMENT_ID', tmpPng);
  const selfieDoc = await uploadDoc(workerToken, 'SELFIE', tmpPng);
  await api(adminToken, 'PATCH', `/verifications/${idDoc.id}/review`, { status: 'APPROVED' });
  await api(adminToken, 'PATCH', `/verifications/${selfieDoc.id}/review`, { status: 'APPROVED' });
  pass('Admin approved ID + selfie');

  const me2 = await api<{
    success: boolean;
    data: { workerProfile: { isVerified: boolean }; phoneVerified: boolean };
  }>(workerToken, 'GET', '/auth/me');
  if (!me2.data.workerProfile.isVerified && !me2.data.phoneVerified) {
    pass('Docs alone do not verify without phone');
  } else {
    fail('Should remain unverified until phone is verified');
  }

  // 3. Portal phone verification
  const sendPhone = await api<{ success: boolean; error?: string }>(
    workerToken,
    'POST',
    '/auth/phone/send-otp',
    { phone }
  );
  if (!sendPhone.success) throw new Error(sendPhone.error || 'phone send-otp failed');
  pass('Phone OTP sent');

  const phoneOtp = await prisma.oTPVerification.findFirst({
    where: { phone, purpose: 'phone_verify', isUsed: false },
    orderBy: { createdAt: 'desc' },
  });
  if (!phoneOtp?.code) throw new Error('Phone OTP not in DB');

  const verifyPhone = await api<{ success: boolean; data: { phoneVerified: boolean } }>(
    workerToken,
    'POST',
    '/auth/phone/verify',
    { phone, otpCode: phoneOtp.code }
  );
  if (verifyPhone.success && verifyPhone.data.phoneVerified) {
    pass('Phone verified in portal');
  } else {
    fail('Phone verify failed');
  }

  const me3 = await api<{
    success: boolean;
    data: {
      phoneVerified: boolean;
      workerProfile: { isVerified: boolean; verificationStatus: string };
    };
  }>(workerToken, 'GET', '/auth/me');

  if (
    me3.data.phoneVerified &&
    me3.data.workerProfile.isVerified &&
    me3.data.workerProfile.verificationStatus === 'APPROVED'
  ) {
    pass('Worker fully verified after phone + approved docs');
  } else {
    fail(
      'Expected full verification',
      JSON.stringify({
        phoneVerified: me3.data.phoneVerified,
        isVerified: me3.data.workerProfile.isVerified,
        status: me3.data.workerProfile.verificationStatus,
      })
    );
  }

  // Public visibility
  const pub = await api<{ success: boolean; data?: { id: string } }>(
    null,
    'GET',
    `/workers/${workerId}`
  );
  if (pub.success && pub.data?.id === workerId) pass('Verified worker is publicly visible');
  else fail('Verified worker should be public');

  await login(email);
  pass('Worker can log in with email');

  console.log('\n── Test account ──');
  console.log(`  ${email} / ${PASS}`);
  console.log(`  phone: ${phone} (verified)`);

  console.log(`\n=== Results: ${passed} passed, ${failed} failed ===`);
  if (failed > 0) process.exit(1);
  console.log('=== ALL PASSED ===\n');
}

main()
  .catch((e) => {
    console.error('\n=== FAILED ===');
    console.error(e);
    process.exit(1);
  })
  .finally(() => prisma.$disconnect());
