import {
  CanActivate,
  ExecutionContext,
  ForbiddenException,
  INestApplication,
  ValidationPipe,
} from '@nestjs/common';
import { Test, TestingModule } from '@nestjs/testing';
import request = require('supertest');

import { AiService } from '../ai/ai.service';
import { CategoriesService } from '../categories/categories.service';
import { ProductsController } from './products.controller';
import { ProductsService } from './products.service';

jest.setTimeout(15000);

class FakeJwtGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    req.user = { id: 42 };
    return true;
  }
}

describe('ProductsController HTTP: PATCH /products/mine/:id/category', () => {
  let app: INestApplication;

  const productsServiceMock = {
    updateCategoryMine: jest.fn(),
  };

  const aiServiceMock = {
    suggestCategory: jest.fn(),
  };

  const categoriesServiceMock = {
    findFlattened: jest.fn(),
  };

  beforeAll(async () => {
    const moduleRef: TestingModule = await Test.createTestingModule({
      controllers: [ProductsController],
      providers: [
        { provide: ProductsService, useValue: productsServiceMock },
        { provide: AiService, useValue: aiServiceMock },
        { provide: CategoriesService, useValue: categoriesServiceMock },
      ],
    }).compile();

    app = moduleRef.createNestApplication();
    app.useGlobalPipes(
      new ValidationPipe({
        whitelist: true,
        forbidNonWhitelisted: true,
        transform: true,
      }),
    );
    app.useGlobalGuards(new FakeJwtGuard());
    await app.init();
  });

  afterAll(async () => {
    await app.close();
  });

  beforeEach(() => {
    jest.clearAllMocks();
    productsServiceMock.updateCategoryMine.mockResolvedValue({ id: 11, categoryId: 5 });
  });

  it('accepterar giltig payload och skickar userId + id + categoryId till service', async () => {
    await request(app.getHttpServer())
      .patch('/products/mine/11/category')
      .send({ categoryId: '5' })
      .expect(200)
      .expect({ id: 11, categoryId: 5 });

    expect(productsServiceMock.updateCategoryMine).toHaveBeenCalledWith(42, 11, 5);
  });

  it('nekar payload utan categoryId', async () => {
    await request(app.getHttpServer())
      .patch('/products/mine/11/category')
      .send({})
      .expect(400);

    expect(productsServiceMock.updateCategoryMine).not.toHaveBeenCalled();
  });

  it('nekar ogiltig path-param id (ParseIntPipe)', async () => {
    await request(app.getHttpServer())
      .patch('/products/mine/not-a-number/category')
      .send({ categoryId: 5 })
      .expect(400);

    expect(productsServiceMock.updateCategoryMine).not.toHaveBeenCalled();
  });

  it('nekar payload med categoryId <= 0', async () => {
    await request(app.getHttpServer())
      .patch('/products/mine/11/category')
      .send({ categoryId: 0 })
      .expect(400);

    expect(productsServiceMock.updateCategoryMine).not.toHaveBeenCalled();
  });

  it('nekar payload med extra fält (whitelist/forbidNonWhitelisted)', async () => {
    await request(app.getHttpServer())
      .patch('/products/mine/11/category')
      .send({ categoryId: 5, isAdmin: true })
      .expect(400);

    expect(productsServiceMock.updateCategoryMine).not.toHaveBeenCalled();
  });

  it('propagerar Forbidden från service', async () => {
    productsServiceMock.updateCategoryMine.mockRejectedValueOnce(
      new ForbiddenException('Du kan inte omkategorisera, ta bort eller ändra produkter som är globala.'),
    );

    const res = await request(app.getHttpServer())
      .patch('/products/mine/11/category')
      .send({ categoryId: 5 })
      .expect(403);

    expect(res.body.message).toBe(
      'Du kan inte omkategorisera, ta bort eller ändra produkter som är globala.',
    );
  });
});
