import {
  Body,
  Controller,
  Delete,
  ForbiddenException,
  Get,
  HttpCode,
  HttpStatus,
  Inject,
  NotFoundException,
  Param,
  Patch,
  Post,
  Query,
  UseGuards,
} from '@nestjs/common';
import type { Brain } from '@mosaicstack/brain';
import { BRAIN } from '../brain/brain.tokens.js';
import { AuthGuard } from '../auth/auth.guard.js';
import { CurrentUser } from '../auth/current-user.decorator.js';
import { assertOwner } from '../auth/resource-ownership.js';
import { CreateTaskDto, UpdateTaskDto } from './tasks.dto.js';

@Controller('api/tasks')
@UseGuards(AuthGuard)
export class TasksController {
  constructor(@Inject(BRAIN) private readonly brain: Brain) {}

  @Get()
  async list(
    @CurrentUser() user: { id: string },
    @Query('projectId') projectId?: string,
    @Query('missionId') missionId?: string,
    @Query('status') status?: string,
  ) {
    if (projectId) {
      await this.getOwnedProject(projectId, user.id, 'Task');
      return this.brain.tasks.findByProject(projectId);
    }
    if (missionId) {
      await this.getOwnedMission(missionId, user.id, 'Task');
      return this.brain.tasks.findByMission(missionId);
    }

    const [projects, missions, tasks] = await Promise.all([
      this.brain.projects.findAll(),
      this.brain.missions.findAll(),
      status
        ? this.brain.tasks.findByStatus(
            status as Parameters<typeof this.brain.tasks.findByStatus>[0],
          )
        : this.brain.tasks.findAll(),
    ]);

    const ownedProjectIds = new Set(
      projects.filter((project) => project.ownerId === user.id).map((project) => project.id),
    );
    const ownedMissionIds = new Set(
      missions
        .filter(
          (ownedMission) =>
            typeof ownedMission.projectId === 'string' &&
            ownedProjectIds.has(ownedMission.projectId),
        )
        .map((ownedMission) => ownedMission.id),
    );

    return tasks.filter(
      (task) =>
        (task.projectId ? ownedProjectIds.has(task.projectId) : false) ||
        (task.missionId ? ownedMissionIds.has(task.missionId) : false),
    );
  }

  @Get(':id')
  async findOne(@Param('id') id: string, @CurrentUser() user: { id: string }) {
    return this.getOwnedTask(id, user.id);
  }

  @Post()
  async create(@Body() dto: CreateTaskDto, @CurrentUser() user: { id: string }) {
    if (dto.projectId) {
      await this.getOwnedProject(dto.projectId, user.id, 'Task');
    }
    if (dto.missionId) {
      await this.getOwnedMission(dto.missionId, user.id, 'Task');
    }
    return this.brain.tasks.create({
      title: dto.title,
      description: dto.description,
      status: dto.status,
      priority: dto.priority,
      projectId: dto.projectId,
      missionId: dto.missionId,
      assignee: dto.assignee,
      tags: dto.tags,
      dueDate: dto.dueDate ? new Date(dto.dueDate) : undefined,
    });
  }

  @Patch(':id')
  async update(
    @Param('id') id: string,
    @Body() dto: UpdateTaskDto,
    @CurrentUser() user: { id: string },
  ) {
    await this.getOwnedTask(id, user.id);
    if (dto.projectId) {
      await this.getOwnedProject(dto.projectId, user.id, 'Task');
    }
    if (dto.missionId) {
      await this.getOwnedMission(dto.missionId, user.id, 'Task');
    }
    const task = await this.brain.tasks.update(id, {
      ...dto,
      dueDate: dto.dueDate ? new Date(dto.dueDate) : dto.dueDate === null ? null : undefined,
    });
    if (!task) throw new NotFoundException('Task not found');
    return task;
  }

  @Delete(':id')
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param('id') id: string, @CurrentUser() user: { id: string }) {
    await this.getOwnedTask(id, user.id);
    const deleted = await this.brain.tasks.remove(id);
    if (!deleted) throw new NotFoundException('Task not found');
  }

  private async getOwnedTask(id: string, userId: string) {
    const task = await this.brain.tasks.findById(id);
    if (!task) throw new NotFoundException('Task not found');

    if (task.projectId) {
      await this.getOwnedProject(task.projectId, userId, 'Task');
      return task;
    }

    if (task.missionId) {
      await this.getOwnedMission(task.missionId, userId, 'Task');
      return task;
    }

    throw new ForbiddenException('Task does not belong to the current user');
  }

  private async getOwnedMission(missionId: string, userId: string, resourceName: string) {
    const mission = await this.brain.missions.findById(missionId);
    if (!mission?.projectId) {
      throw new ForbiddenException(`${resourceName} does not belong to the current user`);
    }

    await this.getOwnedProject(mission.projectId, userId, resourceName);
    return mission;
  }

  private async getOwnedProject(projectId: string, userId: string, resourceName: string) {
    const project = await this.brain.projects.findById(projectId);
    if (!project) {
      throw new ForbiddenException(`${resourceName} does not belong to the current user`);
    }

    assertOwner(project.ownerId, userId, resourceName);
    return project;
  }
}
