// @vitest-environment node
import { afterAll, beforeAll, expect, test, vi } from 'vitest';
import { execSync } from 'node:child_process';
import { Client } from 'pg';

const authModulePath = vi.hoisted(() => new URL('../../auth.ts', import.meta.url).pathname);

vi.unmock('@/lib/prisma');
vi.mock(authModulePath, () => ({
  auth: vi.fn(async () => ({ user: { role: 'admin' } })),
}));
vi.mock('@/lib/auth-check', () => ({
  checkAdminAuth: vi.fn(async () => null),
}));

const testDatabaseUrl = process.env.TEST_DATABASE_URL;
const shouldRun = Boolean(testDatabaseUrl || process.env.RUN_DB_TESTS === '1');
const useDocker = !testDatabaseUrl && process.env.RUN_DB_TESTS === '1';

let prisma: import('@prisma/client').PrismaClient;
let putOrder: typeof import('../../app/api/admin/orders/[id]/route').PUT;
let connectionString = testDatabaseUrl ?? '';
let containerId = '';

const waitForDatabase = async (url: string) => {
  const maxAttempts = 20;
  const delayMs = 500;

  for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
    const client = new Client({ connectionString: url });
    try {
      await client.connect();
      await client.query('SELECT 1');
      await client.end();
      return;
    } catch (error) {
      await client.end().catch(() => undefined);
      if (attempt === maxAttempts) {
        throw error;
      }
      await new Promise((resolve) => setTimeout(resolve, delayMs));
    }
  }
};

if (shouldRun) {
  beforeAll(
    async () => {
    if (useDocker) {
      const containerName = `varjoliitokauppa-test-db-${Date.now()}`;
      containerId = execSync(
        [
          'docker run -d -P',
          '--name',
          containerName,
          '-e POSTGRES_PASSWORD=postgres',
          '-e POSTGRES_USER=postgres',
          '-e POSTGRES_DB=varjoliitokauppa_test',
          'postgres:16-alpine',
        ].join(' ')
      )
        .toString()
        .trim();

      const portOutput = execSync(`docker port ${containerId} 5432/tcp`).toString().trim();
      const port = portOutput.split(':').pop();
      if (!port) {
        throw new Error(`Failed to resolve Postgres port from docker output: ${portOutput}`);
      }
      connectionString = `postgresql://postgres:postgres@127.0.0.1:${port}/varjoliitokauppa_test`;
      await waitForDatabase(connectionString);
    }

    process.env.DIRECT_URL = connectionString;
    process.env.DATABASE_URL = connectionString;

    try {
      execSync(`npx prisma db push --force-reset --accept-data-loss --url "${connectionString}"`, {
        stdio: 'pipe',
        env: {
          ...process.env,
          DIRECT_URL: connectionString,
          DATABASE_URL: connectionString,
        },
      });
    } catch (error) {
      const err = error as { stderr?: Buffer; stdout?: Buffer; message?: string };
      const stderr = err.stderr?.toString();
      const stdout = err.stdout?.toString();
      throw new Error(
        [
          'Prisma db push failed.',
          stdout ? `stdout:\n${stdout}` : null,
          stderr ? `stderr:\n${stderr}` : null,
          err.message ? `message:\n${err.message}` : null,
        ]
          .filter(Boolean)
          .join('\n')
      );
    }

    await vi.resetModules();
    ({ prisma } = await import('../../lib/prisma'));
    ({ PUT: putOrder } = await import('../../app/api/admin/orders/[id]/route'));
    },
    60000
  );

  afterAll(async () => {
    if (prisma) {
      await prisma.$disconnect();
    }
    if (containerId) {
      execSync(`docker rm -f ${containerId}`, { stdio: 'pipe' });
    }
  });
}

const runTest = shouldRun ? test : test.skip;

runTest('reduces stock when order is marked shipped (set TEST_DATABASE_URL or RUN_DB_TESTS=1)', async () => {
  const simpleProduct = await prisma.product.create({
    data: {
      name: 'Perusvaljas',
      price: 800,
      category: 'VALJAAT',
      subcategory: 'Avovaljaat',
      shortDescription: 'Test product',
      description: 'Test product',
      images: ['/valjaat.webp'],
      stock: 5,
      brand: 'Test',
    },
  });

  const variantProduct = await prisma.product.create({
    data: {
      name: 'Gin Atlas 2',
      price: 4200,
      salePrice: 3990,
      category: 'VARJOLIITIMET',
      subcategory: 'EN-B Varjoliitimet',
      shortDescription: 'Variant product',
      description: 'Variant product',
      images: ['/img2.webp'],
      stock: 0,
      brand: 'Gin',
      colors: [
        { name: 'Sininen', hex: '#1e40af', stock: 2 },
        { name: 'Punainen', hex: '#dc2626', stock: 2 },
      ],
      sizes: [
        {
          name: 'M',
          stock: 0,
          colorStocks: [
            { colorName: 'Sininen', stock: 2 },
            { colorName: 'Punainen', stock: 2 },
          ],
        },
      ],
    },
  });

  const order = await prisma.order.create({
    data: {
      customerName: 'Testi Asiakas',
      customerEmail: 'testi@example.com',
      customerPhone: '040 123 4567',
      customerAddress: 'Testikatu 1',
      customerCity: 'Helsinki',
      customerZip: '00100',
      shippingMethod: 'postitus',
      total: 800 + 3990,
      status: 'pending',
      items: {
        create: [
          {
            productId: simpleProduct.id,
            quantity: 2,
            price: 800,
          },
          {
            productId: variantProduct.id,
            quantity: 1,
            price: 3990,
            selectedColor: 'Punainen',
            selectedSize: 'M',
          },
        ],
      },
    },
    include: { items: { include: { product: true } } },
  });

  const request = new Request(`http://localhost/api/admin/orders/${order.id}`, {
    method: 'PUT',
    headers: { 'content-type': 'application/json' },
    body: JSON.stringify({ status: 'shipped' }),
  });

  const response = await putOrder(request, { params: Promise.resolve({ id: order.id }) });
  expect(response.status).toBe(200);

  const updatedSimple = await prisma.product.findUnique({ where: { id: simpleProduct.id } });
  expect(updatedSimple?.stock).toBe(3);

  const updatedVariant = await prisma.product.findUnique({ where: { id: variantProduct.id } });
  const sizes = updatedVariant?.sizes as Array<{
    name: string;
    colorStocks?: Array<{ colorName: string; stock: number }>;
  }>;
  const sizeM = sizes.find((size) => size.name === 'M');
  const redStock = sizeM?.colorStocks?.find((color) => color.colorName === 'Punainen')?.stock;
  expect(redStock).toBe(1);
});
