/* eslint-disable @typescript-eslint/no-explicit-any */
import { INestApplication } from '@nestjs/common';
import request from 'supertest';
import { createApp } from '@/app.factory';
import { DbService } from '@/common/db/db.service';
import { PlatformService } from '@/modules/platform/platform.service';
import { TenantProvisioningService } from '@/modules/platform/tenant-provisioning.service';
import { OutboxDispatcherJob } from '@/modules/jobs/outbox-dispatcher.job';
import { runWithContext, systemContext } from '@/common/context/request-context';
import { plans } from '@/db/schema';
import { eq } from 'drizzle-orm';
import { D } from '@/common/util';

export const PLATFORM_ADMIN = { email: 'admin@platform.test', password: 'Platform!Pass2026' };

/**
 * Boots the real application against the test database and exposes typed
 * helpers. One harness per test file; tenants are provisioned per file with
 * unique codes so files are independent and can run in any order.
 */
export class Harness {
  app!: INestApplication;
  server!: any;
  private counter = 0;

  async start() {
    this.app = await createApp({ swagger: false, logger: false });
    await this.app.init();
    this.server = this.app.getHttpServer();
    await this.seedPlatform();
    return this;
  }

  async stop() {
    await this.app.close();
  }

  private async seedPlatform() {
    const db = this.app.get(DbService);
    const platform = this.app.get(PlatformService);
    await runWithContext(systemContext(null), async () => {
      await db.runBypass(async (tx) => {
        const defs = [
          { code: 'STARTER', name: 'Starter', monthlyPrice: '299', limits: { maxWarehouses: 1, maxUsers: 10, maxStorers: 5, maxOrdersPerMonth: 5000, maxApiCallsPerDay: 20000, aiTokensPerMonth: 0 }, features: ['billing.3pl', 'integrations.api', 'outbound.waves'] },
          { code: 'ENTERPRISE', name: 'Enterprise', monthlyPrice: '2500', limits: { maxWarehouses: 50, maxUsers: 1000, maxStorers: 1000, maxOrdersPerMonth: 10000000, maxApiCallsPerDay: 5000000, aiTokensPerMonth: 20000000 }, features: ['billing.3pl', 'integrations.api', 'integrations.webhooks', 'integrations.sap', 'outbound.waves', 'inventory.serials', 'customer_portal', 'ai.document_intake', 'ai.anomaly_detection', 'ai.nl_query', 'ai.slotting', 'ai.forecast'] },
        ];
        for (const p of defs) {
          const [existing] = await tx.select({ id: plans.id }).from(plans).where(eq(plans.code, p.code)).limit(1);
          if (!existing) await tx.insert(plans).values({ ...p, monthlyPrice: D(p.monthlyPrice), currency: 'USD' });
        }
      });
      await platform.ensureBootstrapAdmin(PLATFORM_ADMIN.email, PLATFORM_ADMIN.password);
    });
  }

  /** Provisions a tenant with an admin user and first warehouse; returns a logged-in client. */
  async tenant(prefix: string, planCode: 'STARTER' | 'ENTERPRISE' = 'ENTERPRISE'): Promise<TenantClient> {
    const code = `${prefix}-${Date.now().toString(36)}-${++this.counter}`.toLowerCase();
    const email = `admin@${code}.test`;
    const password = 'Admin!Pass2026';
    const provisioning = this.app.get(TenantProvisioningService);
    await runWithContext(systemContext(null), () =>
      provisioning.provision({ code, name: `Tenant ${code}`, planCode, timezone: 'Asia/Kuwait', currency: 'KWD', locale: 'en', adminEmail: email, adminName: 'Admin', adminPassword: password, firstWarehouse: { code: 'KW1', name: 'Main' }, trialDays: 0 } as any),
    );
    const client = new TenantClient(this, code, email, password);
    await client.login();
    return client;
  }

  async platformToken(): Promise<string> {
    const res = await request(this.server).post('/api/auth/platform/login').send(PLATFORM_ADMIN).expect(200);
    return res.body.accessToken;
  }

  /** Runs the outbox dispatcher until the queue is drained (jobs are disabled in tests). */
  async drainOutbox(maxRounds = 10): Promise<void> {
    const job = this.app.get(OutboxDispatcherJob);
    for (let i = 0; i < maxRounds; i++) {
      const n = await job.tick();
      if (!n) return;
    }
  }
}

export class TenantClient {
  token = '';
  profile: any = null;
  warehouseId = '';

  constructor(readonly h: Harness, readonly code: string, readonly email: string, readonly password: string) {}

  async login() {
    const res = await request(this.h.server).post('/api/auth/login').send({ tenantCode: this.code, email: this.email, password: this.password }).expect(200);
    this.token = res.body.tokens.accessToken;
    this.profile = res.body.profile;
    this.warehouseId = this.profile.warehouses[0].id;
    return res.body;
  }

  private auth(req: request.Test, token?: string) {
    return req.set('authorization', `Bearer ${token ?? this.token}`);
  }

  get(path: string, query?: Record<string, unknown>, token?: string) {
    return this.auth(request(this.h.server).get(`/api${path}`).query(query ?? {}), token);
  }
  post(path: string, body?: unknown, token?: string) {
    return this.auth(request(this.h.server).post(`/api${path}`).send(body as object), token);
  }
  patch(path: string, body?: unknown, token?: string) {
    return this.auth(request(this.h.server).patch(`/api${path}`).send(body as object), token);
  }
  put(path: string, body?: unknown, token?: string) {
    return this.auth(request(this.h.server).put(`/api${path}`).send(body as object), token);
  }
  delete(path: string, token?: string) {
    return this.auth(request(this.h.server).delete(`/api${path}`), token);
  }
  apiKey(key: string) {
    return {
      get: (path: string, query?: Record<string, unknown>) => request(this.h.server).get(`/api${path}`).query(query ?? {}).set('x-api-key', key),
      post: (path: string, body?: unknown, headers: Record<string, string> = {}) => request(this.h.server).post(`/api${path}`).set('x-api-key', key).set(headers).send(body as object),
      put: (path: string, body?: unknown) => request(this.h.server).put(`/api${path}`).set('x-api-key', key).send(body as object),
    };
  }

  // ---- fixtures used by several suites ----

  /** Storer + zone locations + one lot/expiry controlled SKU with CASE pack (the smoke-test world). */
  async standardWorld() {
    const storer = (await this.post('/storers', { code: 'ACME', name: 'Acme Trading', settings: { receivingTolerancePct: 10, rcrEmails: ['ops@acme.test'], autoSendRcr: true } }).expect(201)).body;
    const zones = (await this.get(`/warehouses/${this.warehouseId}/zones`).expect(200)).body;
    const amb = zones.find((z: any) => z.code === 'AMB');
    await this.post('/locations/generate', { warehouseId: this.warehouseId, zoneId: amb.id, aisles: ['A'], baysFrom: 1, baysTo: 3, levels: ['1', '2'], maxLpns: 1, groundLevelIsPick: true }).expect(201);
    const sku = (
      await this.post('/skus', {
        storerId: storer.id, code: 'WIDGET-1', description: 'Widget 1', lotControlled: true, expiryControlled: true, cbm: '0.002', weightKg: '0.5',
        packs: [{ level: 'CASE', uomCode: 'CS', qtyInBase: '12', barcode: 'CS-WIDGET-1' }, { level: 'PALLET', uomCode: 'PL', qtyInBase: '480', palletTi: 10, palletHi: 4 }],
        barcodes: [{ barcode: '6291000000011', packLevel: 'EACH', isPrimary: true }],
      }).expect(201)
    ).body;
    return { storer, sku, zone: amb };
  }

  /** Receives 8 good + 1 damaged cases of WIDGET-1 against a 10-case ASN and puts the good pallet away. */
  async receiveStandardAsn(storerId: string) {
    const asn = (await this.post('/inbound/asns', { warehouseId: this.warehouseId, storerId, externalRef: 'PO-1001', containerSize: '40', lines: [{ skuCode: 'WIDGET-1', expectedQty: '10', packLevel: 'CASE', expectedLot: 'L1' }] }).expect(201)).body;
    await this.post(`/inbound/asns/${asn.id}/arrive`, { vehicleNo: 'KWT 1234', sealNo: 'S-77' }).expect(201);
    const lineId = asn.lines[0].id;
    const r1 = (await this.post(`/inbound/asns/${asn.id}/receive`, { asnLineId: lineId, barcode: 'CS-WIDGET-1', qty: '8', lotNumber: 'L1', expiryDate: '2027-01-01', idempotencyKey: 'rcv-1' }).expect(201)).body;
    const rc = (await this.get('/reason-codes', { type: 'DAMAGE' }).expect(200)).body[0];
    await this.post(`/inbound/asns/${asn.id}/receive`, { asnLineId: lineId, qty: '1', packLevel: 'CASE', lotNumber: 'L1', expiryDate: '2027-01-01', condition: 'DAMAGED', reasonCodeId: rc.id }).expect(201);
    const next = (await this.post('/tasks/next', { warehouseId: this.warehouseId, types: ['PUTAWAY'] }).expect(201)).body;
    await this.post(`/tasks/${next.task.id}/confirm`, { toLocationCode: next.task.toLocationCode, lpnNumber: next.task.lpnNumber }).expect(201);
    return { asn, receipt: r1, putawayTask: next.task };
  }
}
