import { ForbiddenException, NotFoundException } from '@nestjs/common';
import { describe, expect, it, vi } from 'vitest';
import { ConversationsController } from '../conversations/conversations.controller.js';
import { MissionsController } from '../missions/missions.controller.js';
import { ProjectsController } from '../projects/projects.controller.js';
import { TasksController } from '../tasks/tasks.controller.js';

function createBrain() {
  return {
    conversations: {
      findAll: vi.fn(),
      findById: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      remove: vi.fn(),
      findMessages: vi.fn(),
      addMessage: vi.fn(),
    },
    projects: {
      findAll: vi.fn(),
      findAllForUser: vi.fn(),
      findById: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      remove: vi.fn(),
    },
    missions: {
      findAll: vi.fn(),
      findAllByUser: vi.fn(),
      findById: vi.fn(),
      findByIdAndUser: vi.fn(),
      findByProject: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      remove: vi.fn(),
    },
    missionTasks: {
      findByMissionAndUser: vi.fn(),
      findByIdAndUser: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      remove: vi.fn(),
    },
    tasks: {
      findAll: vi.fn(),
      findById: vi.fn(),
      findByProject: vi.fn(),
      findByMission: vi.fn(),
      findByStatus: vi.fn(),
      create: vi.fn(),
      update: vi.fn(),
      remove: vi.fn(),
    },
  };
}

describe('Resource ownership checks', () => {
  it('forbids access to another user conversation', async () => {
    const brain = createBrain();
    // The repo enforces ownership via the WHERE clause; it returns undefined when the
    // conversation does not belong to the requesting user.
    brain.conversations.findById.mockResolvedValue(undefined);
    const controller = new ConversationsController(brain as never);

    await expect(controller.findOne('conv-1', { id: 'user-1' })).rejects.toBeInstanceOf(
      NotFoundException,
    );
  });

  it('forbids access to another user project', async () => {
    const brain = createBrain();
    brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' });
    const teamsService = { canAccessProject: vi.fn().mockResolvedValue(false) };
    const controller = new ProjectsController(brain as never, teamsService as never);

    await expect(controller.findOne('project-1', { id: 'user-1' })).rejects.toBeInstanceOf(
      ForbiddenException,
    );
  });

  it('forbids access to a mission owned by another user', async () => {
    const brain = createBrain();
    // findByIdAndUser returns undefined when the mission doesn't belong to the user
    brain.missions.findByIdAndUser.mockResolvedValue(undefined);
    const controller = new MissionsController(brain as never);

    await expect(controller.findOne('mission-1', { id: 'user-1' })).rejects.toBeInstanceOf(
      NotFoundException,
    );
  });

  it('forbids access to a task owned by another project owner', async () => {
    const brain = createBrain();
    brain.tasks.findById.mockResolvedValue({ id: 'task-1', projectId: 'project-1' });
    brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' });
    const controller = new TasksController(brain as never);

    await expect(controller.findOne('task-1', { id: 'user-1' })).rejects.toBeInstanceOf(
      ForbiddenException,
    );
  });

  it('forbids creating a task with an unowned project', async () => {
    const brain = createBrain();
    brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' });
    const controller = new TasksController(brain as never);

    await expect(
      controller.create(
        {
          title: 'Task',
          projectId: 'project-1',
        },
        { id: 'user-1' },
      ),
    ).rejects.toBeInstanceOf(ForbiddenException);
  });

  it('forbids listing tasks for an unowned project', async () => {
    const brain = createBrain();
    brain.projects.findById.mockResolvedValue({ id: 'project-1', ownerId: 'user-2' });
    const controller = new TasksController(brain as never);

    await expect(
      controller.list({ id: 'user-1' }, 'project-1', undefined, undefined),
    ).rejects.toBeInstanceOf(ForbiddenException);
  });

  it('lists only tasks for the current user owned projects when no filter is provided', async () => {
    const brain = createBrain();
    brain.projects.findAll.mockResolvedValue([
      { id: 'project-1', ownerId: 'user-1' },
      { id: 'project-2', ownerId: 'user-2' },
    ]);
    brain.missions.findAll.mockResolvedValue([{ id: 'mission-1', projectId: 'project-1' }]);
    brain.tasks.findAll.mockResolvedValue([
      { id: 'task-1', projectId: 'project-1' },
      { id: 'task-2', missionId: 'mission-1' },
      { id: 'task-3', projectId: 'project-2' },
    ]);
    const controller = new TasksController(brain as never);

    await expect(
      controller.list({ id: 'user-1' }, undefined, undefined, undefined),
    ).resolves.toEqual([
      { id: 'task-1', projectId: 'project-1' },
      { id: 'task-2', missionId: 'mission-1' },
    ]);
  });
});
