import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();
const API_URL = process.env.REAL_API_URL ?? 'http://localhost:3000/api/v1';
const RUN_ID = `real-${Date.now()}`;

type Session = {
  accessToken: string;
  refreshToken: string;
  user: {
    id: string;
    email: string;
    tenantId?: string | null;
    role?: string;
    roles?: Array<{ code: string; tenantId?: string | null }>;
  };
};

type ApiOptions = {
  token?: string;
  method?: 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
  body?: Record<string, unknown>;
  expected?: number | number[];
};

const created = {
  complexId: '',
  spatialUnitId: '',
  repartitionKeyId: '',
  fiscalYearId: '',
  budgetId: '',
  budgetLineId: '',
  lotId: '',
  residentId: '',
  residentUserId: '',
  vehicleId: '',
  ticketId: '',
  supplierId: '',
  contractId: '',
  workId: '',
  bankAccountId: '',
  bankTransactionId: '',
  commonSpaceId: '',
  bookingId: '',
  maintenancePlanId: '',
  maintenanceEventId: '',
};

function assert(condition: unknown, message: string): asserts condition {
  if (!condition) throw new Error(message);
}

function hasRole(session: Session, role: string) {
  return session.user.role === role || session.user.roles?.some((item) => item.code === role);
}

function tenantId(session: Session) {
  return session.user.tenantId ?? session.user.roles?.find((item) => item.tenantId)?.tenantId ?? null;
}

function idWhere(id: string) {
  return { id: id || '__not-created__' };
}

async function api<T>(path: string, options: ApiOptions = {}): Promise<T> {
  const expected = Array.isArray(options.expected)
    ? options.expected
    : [options.expected ?? 200, ...(options.expected ? [] : [201])];
  const response = await fetch(`${API_URL}${path}`, {
    method: options.method ?? (options.body ? 'POST' : 'GET'),
    headers: {
      'Content-Type': 'application/json',
      ...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });
  const text = await response.text();
  const data = text ? JSON.parse(text) : {};
  if (!expected.includes(response.status)) {
    throw new Error(`${options.method ?? 'GET'} ${path} expected ${expected.join('/')} got ${response.status}: ${text}`);
  }
  return data as T;
}

async function login(email: string, password: string) {
  return api<Session>('/auth/login', {
    method: 'POST',
    body: { email, password },
  });
}

async function main() {
  console.log(`Real API smoke run: ${RUN_ID}`);

  const admin = await login('admin@syndiclub.com', 'Admin@12345');
  const syndic = await login('syndic@atlas.ma', 'Syndic@12345');
  const copro = await login('copro@atlas.ma', 'Test@12345');
  const locataire = await login('locataire@atlas.ma', 'Test@12345');
  const gardien = await login('gardien@atlas.ma', 'Test@12345');
  const prestataire = await login('prestataire@atlas.ma', 'Test@12345');
  assert(hasRole(admin, 'SUPERADMIN'), 'Superadmin login did not return SUPERADMIN role');
  assert(tenantId(syndic), 'Syndic must have tenant context');

  await api('/tenants', { token: syndic.accessToken, expected: 403 });
  await api('/complexes', { token: copro.accessToken, method: 'POST', body: { name: 'Forbidden', type: 'COPROPRIETE' }, expected: 403 });

  const complex = await api<{ id: string }>('/complexes', {
    token: syndic.accessToken,
    body: {
      name: `Résidence ${RUN_ID}`,
      type: 'COPROPRIETE',
      address: '1 Rue Test',
      city: 'Casablanca',
      country: 'MA',
    },
  });
  created.complexId = complex.id;

  const spatialUnit = await api<{ id: string }>(`/complexes/${complex.id}/spatial-units`, {
    token: syndic.accessToken,
    body: { type: 'BATIMENT', code: 'T', label: `Bâtiment ${RUN_ID}` },
  });
  created.spatialUnitId = spatialUnit.id;

  const repartitionKey = await api<{ id: string }>(`/complexes/${complex.id}/repartition-keys`, {
    token: syndic.accessToken,
    body: { code: `GEN_${RUN_ID}`, label: `Charges ${RUN_ID}`, type: 'GENERALE' },
  });
  created.repartitionKeyId = repartitionKey.id;

  const fiscalYear = await api<{ id: string }>('/fiscal-years', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      label: `FY ${RUN_ID}`,
      startDate: '2026-01-01',
      endDate: '2026-12-31',
    },
  });
  created.fiscalYearId = fiscalYear.id;

  const budget = await api<{ id: string }>(`/fiscal-years/${fiscalYear.id}/budget`, {
    token: syndic.accessToken,
    method: 'PUT',
    body: { totalAmount: 12000 },
  });
  created.budgetId = budget.id;

  const budgetLine = await api<{ id: string }>(`/budgets/${budget.id}/lines`, {
    token: syndic.accessToken,
    body: { category: 'Nettoyage', repartitionKeyId: repartitionKey.id, estimatedAmount: 12000, notes: RUN_ID },
  });
  created.budgetLineId = budgetLine.id;

  const lot = await api<{ id: string }>('/lots', {
    token: syndic.accessToken,
    body: {
      spatialUnitId: spatialUnit.id,
      lotNumber: `LOT-${RUN_ID}`,
      type: 'APPARTEMENT',
      surfaceM2: 88,
      tantiemesGeneral: 100,
    },
  });
  created.lotId = lot.id;

  await api(`/lots/${lot.id}/tantiemes`, {
    token: syndic.accessToken,
    body: { repartitionKeyId: repartitionKey.id, quota: 100 },
  });

  const resident = await api<{ id: string; user: { id: string; email: string } }>('/residents', {
    token: syndic.accessToken,
    body: {
      lotId: lot.id,
      role: 'PROPRIETAIRE',
      email: `${RUN_ID}@resident.test`,
      firstName: 'Real',
      lastName: 'Resident',
      password: 'Real@12345',
      isPrimary: true,
    },
  });
  created.residentId = resident.id;
  created.residentUserId = resident.user.id;
  await login(`${RUN_ID}@resident.test`, 'Real@12345');

  const vehicle = await api<{ id: string }>(`/lots/${lot.id}/vehicles`, {
    token: syndic.accessToken,
    body: { residentId: resident.id, plateNumber: `API-${RUN_ID.slice(-6)}`, brand: 'Dacia', model: 'Sandero' },
  });
  created.vehicleId = vehicle.id;

  const fundCall = await api<{ id: string; itemsCount: number }>('/fund-calls', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      fiscalYearId: fiscalYear.id,
      label: `Appel ${RUN_ID}`,
      period: RUN_ID,
      dueDate: '2026-06-30',
      allocations: [{ repartitionKeyId: repartitionKey.id, amount: 1000 }],
    },
  });
  assert(fundCall.itemsCount === 1, 'Fund call should generate one item for one lot');

  const supplier = await api<{ id: string }>('/suppliers', {
    token: syndic.accessToken,
    body: { name: `Prestataire ${RUN_ID}`, legalId: RUN_ID, contact: 'ops@example.test' },
  });
  created.supplierId = supplier.id;

  const contract = await api<{ id: string }>('/contracts', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      supplierId: supplier.id,
      title: `Contrat ${RUN_ID}`,
      category: 'Nettoyage',
      startDate: '2026-01-01',
      endDate: '2026-12-31',
      amount: 5000,
      billingFreq: 'MONTHLY',
    },
  });
  created.contractId = contract.id;

  const work = await api<{ id: string }>('/works', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      title: `Travaux ${RUN_ID}`,
      description: 'Validation réelle API/DB',
      budget: 25000,
      startDate: '2026-02-01',
      contractorId: supplier.id,
    },
  });
  created.workId = work.id;

  const bankAccount = await api<{ id: string }>('/bank/accounts', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      bankName: 'Attijariwafa',
      accountName: `Compte ${RUN_ID}`,
      rib: '123456789012345678901234',
      initialBalance: 1000,
      currency: 'MAD',
      isDefault: true,
    },
  });
  created.bankAccountId = bankAccount.id;

  const bankTransaction = await api<{ id: string }>('/bank/transactions', {
    token: syndic.accessToken,
    body: {
      bankAccountId: bankAccount.id,
      date: '2026-03-01',
      label: `Virement ${RUN_ID}`,
      amount: 1000,
      type: 'CREDIT',
    },
  });
  created.bankTransactionId = bankTransaction.id;
  await api(`/bank/transactions/${bankTransaction.id}/status`, {
    token: syndic.accessToken,
    method: 'PUT',
    body: { status: 'RECONCILED' },
  });

  const commonSpace = await api<{ id: string }>('/common-spaces', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      name: `Salle ${RUN_ID}`,
      type: 'SALLE_REUNION',
      capacity: 20,
      priceType: 'FLAT_RATE',
      flatFee: 100,
      isBookable: true,
    },
  });
  created.commonSpaceId = commonSpace.id;

  const startAt = new Date(Date.now() + 10 * 24 * 60 * 60 * 1000);
  startAt.setUTCHours(10, 0, 0, 0);
  const endAt = new Date(startAt.getTime() + 2 * 60 * 60 * 1000);
  const booking = await api<{ id: string }>('/bookings', {
    token: copro.accessToken,
    body: { spaceId: commonSpace.id, startAt: startAt.toISOString(), endAt: endAt.toISOString(), notes: RUN_ID },
  });
  created.bookingId = booking.id;
  await api(`/bookings/${booking.id}/confirm`, { token: syndic.accessToken, method: 'PATCH' });

  const ticket = await api<{ id: string }>('/tickets', {
    token: copro.accessToken,
    body: {
      complexId: complex.id,
      lotId: lot.id,
      category: 'PLOMBERIE',
      priority: 'HIGH',
      title: `Ticket ${RUN_ID}`,
      description: 'Fuite validée en test réel',
      location: 'Bâtiment T',
    },
  });
  created.ticketId = ticket.id;
  await api(`/tickets/${ticket.id}/messages`, {
    token: syndic.accessToken,
    body: { body: `Message ${RUN_ID}`, isInternal: false },
  });
  await api(`/tickets/${ticket.id}/assign`, {
    token: syndic.accessToken,
    method: 'PATCH',
    body: { assigneeId: prestataire.user.id },
  });
  await api(`/tickets/${ticket.id}/status`, {
    token: prestataire.accessToken,
    method: 'PATCH',
    body: { status: 'IN_PROGRESS' },
  });
  await api(`/tickets/${ticket.id}/status`, {
    token: syndic.accessToken,
    method: 'PATCH',
    body: { status: 'CLOSED' },
  });

  const maintenancePlan = await api<{ id: string }>('/maintenance-plans', {
    token: syndic.accessToken,
    body: {
      complexId: complex.id,
      title: `Plan ${RUN_ID}`,
      equipment: 'Ascenseur API',
      frequencyDays: 30,
      nextDueDate: '2026-04-01',
      providerId: supplier.id,
    },
  });
  created.maintenancePlanId = maintenancePlan.id;
  await api('/maintenance/run-now', { token: syndic.accessToken, method: 'POST' });
  const events = await api<Array<{ id: string; planId: string }>>(`/maintenance-events?complexId=${complex.id}`, {
    token: syndic.accessToken,
  });
  const event = events.find((item) => item.planId === maintenancePlan.id);
  assert(event, 'Maintenance run should create an event');
  created.maintenanceEventId = event.id;
  await api(`/maintenance-events/${event.id}/complete`, {
    token: prestataire.accessToken,
    method: 'PATCH',
    body: { notes: RUN_ID, reportUrl: 'https://example.test/report.pdf' },
  });

  await api('/tickets', { token: gardien.accessToken, expected: 200 });
  await api('/bookings?mine=true', { token: locataire.accessToken, expected: 200 });

  const dbComplex = await prisma.complex.findUnique({ where: { id: complex.id } });
  const dbTicket = await prisma.ticket.findUnique({ where: { id: ticket.id } });
  const dbBooking = await prisma.booking.findUnique({ where: { id: booking.id } });
  const dbTransaction = await prisma.bankTransaction.findUnique({ where: { id: bankTransaction.id } });
  assert(dbComplex?.name.includes(RUN_ID), 'DB complex was not persisted');
  assert(dbTicket?.status === 'CLOSED', 'DB ticket status was not persisted');
  assert(dbBooking?.status === 'CONFIRMED', 'DB booking status was not persisted');
  assert(dbTransaction?.status === 'RECONCILED', 'DB bank transaction status was not persisted');

  console.log('Real API + DB smoke passed');
}

async function cleanup() {
  await prisma.$transaction([
    prisma.booking.deleteMany({ where: idWhere(created.bookingId) }),
    prisma.commonSpace.deleteMany({ where: idWhere(created.commonSpaceId) }),
    prisma.maintenanceEvent.deleteMany({ where: idWhere(created.maintenanceEventId) }),
    prisma.maintenancePlan.deleteMany({ where: idWhere(created.maintenancePlanId) }),
    prisma.ticketMessage.deleteMany({ where: { ticketId: created.ticketId || '__not-created__' } }),
    prisma.ticket.deleteMany({ where: idWhere(created.ticketId) }),
    prisma.bankTransaction.deleteMany({ where: idWhere(created.bankTransactionId) }),
    prisma.bankAccount.deleteMany({ where: idWhere(created.bankAccountId) }),
    prisma.contract.deleteMany({ where: idWhere(created.contractId) }),
    prisma.workProject.deleteMany({ where: idWhere(created.workId) }),
    prisma.fundCall.deleteMany({ where: { label: { contains: RUN_ID } } }),
    prisma.budgetLine.deleteMany({ where: idWhere(created.budgetLineId) }),
    prisma.budget.deleteMany({ where: idWhere(created.budgetId) }),
    prisma.fiscalYear.deleteMany({ where: idWhere(created.fiscalYearId) }),
    prisma.vehicle.deleteMany({ where: idWhere(created.vehicleId) }),
    prisma.resident.deleteMany({ where: idWhere(created.residentId) }),
    prisma.userRole.deleteMany({ where: { userId: created.residentUserId || '__not-created__' } }),
    prisma.user.deleteMany({ where: idWhere(created.residentUserId) }),
    prisma.lotTantieme.deleteMany({ where: { lotId: created.lotId || '__not-created__' } }),
    prisma.lot.deleteMany({ where: idWhere(created.lotId) }),
    prisma.supplier.deleteMany({ where: idWhere(created.supplierId) }),
    prisma.repartitionKey.deleteMany({ where: idWhere(created.repartitionKeyId) }),
    prisma.spatialUnit.deleteMany({ where: idWhere(created.spatialUnitId) }),
    prisma.complex.deleteMany({ where: idWhere(created.complexId) }),
  ]);
}

main()
  .catch(async (error) => {
    console.error(error);
    process.exitCode = 1;
  })
  .finally(async () => {
    await cleanup().catch((error) => {
      console.error('Cleanup failed', error);
      process.exitCode = 1;
    });
    await prisma.$disconnect();
  });
