import {
  Controller,
  Get,
  Post,
  Patch,
  Delete,
  Body,
  Param,
  Query,
  ParseUUIDPipe,
  HttpCode,
  HttpStatus,
  UseInterceptors,
  UseGuards,
  UploadedFiles,
  BadRequestException,
  NotFoundException,
} from "@nestjs/common";
import { AnyFilesInterceptor } from "@nestjs/platform-express";
import { ApiOperation, ApiTags, ApiConsumes } from "@nestjs/swagger";
import { CurrentUser } from "@edr/api-common";
import type { TCurrentUser } from "@tria-plc/api-common/modules/auth/types/current-user.type";
import { JwtGuard } from "@tria-plc/api-common/modules/auth/services/jwt.guard";
import { BookingStaff } from "../../common/booking-guards";
import {
  assertFreightPermission,
  hasFreightPermission,
} from "../../common/freight-permission.util";
import { FREIGHT_PERMS } from "../../seed/freight-permissions.registry";
import { FilesService } from "../files/files.service";
import { CompaniesService } from "./companies.service";
import { CreateCompanyDto } from "./dto/create-company.dto";
import { UpdateCompanyDto } from "./dto/update-company.dto";
import { CreateExternalProfileDto } from "./dto/create-external-profile.dto";
import { CreateCompanyWithProfileDto } from "./dto/create-company-with-profile.dto";
import { AddCompanyProfilesDto } from "./dto/add-company-profiles.dto";
import { CreateCompanyProfileDto } from "./dto/create-company-profile.dto";
import { SetOnboardingStepDto } from "./dto/set-onboarding-step.dto";
import { StartOnboardingDto } from "./dto/start-onboarding.dto";
import { DashboardQueryDto } from "./dto/dashboard-query.dto";
import {
  ResponseCompanyDto,
  ResponseCompanyProfileDto,
} from "./dto/response-company.dto";
import {
  CompanyDocumentFileView,
  ProfileLicenseFileView,
} from "./entities/company-profile.entity";
import { ResponseExternalProfileDto } from "./dto/response-external-profile.dto";
import { CompanyInfoResponseDto } from "./dto/company-info-response.dto";
import { UpdateProfileDto } from "./dto/update-profile.dto";
import { ProfileResponseDto } from "./dto/profile-response.dto";
import { DashboardSummaryResponseDto } from "./dto/dashboard-summary-response.dto";
import { ListCompaniesQueryDto } from "./dto/list-companies-query.dto";
import { CompanyStatsResponseDto } from "./dto/company-stats-response.dto";
import { OnboardingRequirementsResponseDto } from "./dto/onboarding-requirements-response.dto";
import { UpdateCompanyProfileStatusDto } from "./dto/update-company-profile-status.dto";
import { RejectChangeRequestDto } from "./dto/reject-change-request.dto";
import { RequestDocumentChangeDto } from "./dto/request-document-change.dto";
import { ChangeRequestResponseDto } from "./dto/change-request-response.dto";
import { FetchETradeDto } from "./dto/fetch-etrade.dto";
import { ETradeResponseDto } from "./dto/etrade-response.dto";

interface CurrentIamUser {
  id: string;
  name?: { en: string; am: string };
  email?: string;
  phoneNumber?: string;
}

/**
 * Which permission a status write needs. Approving/reactivating is a different
 * authority from suspending, but both arrive on the same route with the target
 * in the BODY — a route-level guard can't tell them apart, so the handlers
 * assert against this map instead.
 *
 * Keyed by string so it serves both `CompanyStatus` and `ProfileStatus`
 * (a superset: it adds `rejected`).
 */
const STATUS_PERM: Record<string, string> = {
  active: FREIGHT_PERMS.customers.verify,
  pending: FREIGHT_PERMS.customers.verify,
  rejected: FREIGHT_PERMS.customers.verify,
  suspended: FREIGHT_PERMS.customers.deactivate,
  blacklisted: FREIGHT_PERMS.customers.deactivate,
};

@ApiTags("Companies")
@Controller("companies")
export class CompaniesController {
  constructor(
    private readonly companiesService: CompaniesService,
    private readonly filesService: FilesService,
  ) { }

  /**
   * License files are FileRecord-backed and previewed through `GET /api/files/:id`
   * (the client builds that URL from the returned `id`). Populate each profile
   * DTO's `licenseFiles` with its live/pending files in one batched lookup.
   */
  private async populateLicenseFiles(
    companyId: string,
    profiles: { id: string; licenseFiles: ProfileLicenseFileView[] }[],
  ): Promise<void> {
    if (profiles.length === 0) return;
    const byProfile = await this.companiesService.assembleLicenseFilesByProfile(
      companyId,
      profiles.map((p) => p.id),
    );
    for (const p of profiles) {
      p.licenseFiles = byProfile[p.id] ?? [];
    }
  }

  @Get("getInfo")
  @ApiOperation({ summary: "Get company info for the current user" })
  async getInfo(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<CompanyInfoResponseDto> {
    const { profile, company } =
      await this.companiesService.getCompanyInfoByUserId(user.id);
    const review = await this.companiesService.getOpenChangeRequestForCompany(
      company.id,
    );
    return new CompanyInfoResponseDto(profile, company, review);
  }

  @Get("profile")
  @ApiOperation({ summary: "Get flattened profile for the settings page" })
  async getProfile(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<ProfileResponseDto> {
    const { profile, company } =
      await this.companiesService.getCompanyInfoByUserId(user.id);
    const review = await this.companiesService.getOpenChangeRequestForCompany(
      company.id,
    );
    const dto = new ProfileResponseDto(profile, company, review);
    await this.populateLicenseFiles(company.id, dto.companyProfiles);
    return dto;
  }

  @Get("profile/change-request")
  @ApiOperation({
    summary: "Current user's open profile change request (pending/rejected)",
  })
  async getMyChangeRequest(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<ChangeRequestResponseDto | null> {
    const { company } =
      await this.companiesService.getCompanyInfoByUserId(user.id);
    const review = await this.companiesService.getOpenChangeRequestForCompany(
      company.id,
    );
    return review ? new ChangeRequestResponseDto(review) : null;
  }

  @Post("company-profiles/:profileId/reapply")
  @ApiOperation({
    summary: "Resubmit a rejected operational role for approval (→ pending)",
  })
  async reapplyCompanyProfile(
    @CurrentUser() user: CurrentIamUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
  ): Promise<ResponseCompanyProfileDto> {
    const profile = await this.companiesService.reapplyCompanyProfile(
      user.id,
      profileId,
    );
    return new ResponseCompanyProfileDto(profile);
  }

  @Get("dashboard")
  @ApiOperation({
    summary:
      "Get portal dashboard KPIs (delivered, spend, freight volume) for the current user",
  })
  async getDashboard(
    @CurrentUser() user: CurrentIamUser,
    @Query() query: DashboardQueryDto,
  ): Promise<DashboardSummaryResponseDto> {
    return this.companiesService.getDashboardSummary(
      user.id,
      query.companyProfileId,
    );
  }

  @Post("fetch-etrade-info")
  @ApiOperation({ summary: "Fetch company info from eTrade by TIN" })
  async fetchETradeInfo(
    @Body() dto: FetchETradeDto,
  ): Promise<ETradeResponseDto> {
    const data = await this.companiesService.fetchETradeData(dto.tin);
    return new ETradeResponseDto(data);
  }

  @Patch("profile")
  @ApiOperation({ summary: "Update profile (flattened settings page)" })
  async updateProfile(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: UpdateProfileDto,
  ): Promise<ProfileResponseDto> {
    return this.companiesService.updateProfile(user.id, dto);
  }

  @Post("company-profiles")
  @ApiOperation({
    summary:
      "Add operational profile(s) (importer/exporter/forwarder) to the current user's company",
  })
  async addCompanyProfiles(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: AddCompanyProfilesDto,
  ): Promise<ResponseCompanyProfileDto[]> {
    const profiles = await this.companiesService.addCompanyProfilesForUser(
      user.id,
      dto.types,
    );
    return profiles.map((p) => new ResponseCompanyProfileDto(p));
  }

  @Post("onboarding/start")
  @ApiOperation({
    summary:
      "Begin onboarding: create a draft company + profile + role(s) so later steps can save incrementally",
  })
  async startOnboarding(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: StartOnboardingDto,
  ): Promise<CompanyInfoResponseDto> {
    const nameParts = (user.name?.en ?? "").split(" ");
    const { profile, company } = await this.companiesService.startOnboarding(
      {
        userId: user.id,
        firstName: nameParts[0] || "",
        lastName: nameParts.slice(-1)[0] || "",
        email: user.email ?? "",
        phone: user.phoneNumber ?? "",
      },
      dto.companyType,
      dto.roles,
      dto.nationality,
    );
    return new CompanyInfoResponseDto(profile, company);
  }

  @Post("company-profile")
  @ApiOperation({
    summary:
      "Create a single operational profile for the current user's company. The role starts pending and does not become the active mode",
  })
  async createCompanyProfile(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: CreateCompanyProfileDto,
  ): Promise<ResponseCompanyProfileDto> {
    const profile = await this.companiesService.createCompanyProfileForUser(
      user.id,
      dto.type,
      dto.businessLicense,
    );
    return new ResponseCompanyProfileDto(profile);
  }

  @Post("company-profiles/:profileId/license")
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes("multipart/form-data")
  @ApiOperation({
    summary:
      "Add business-license document(s) to a profile. For an approved company " +
      "the upload is staged for backoffice review; during onboarding it goes live.",
  })
  async uploadProfileLicense(
    @CurrentUser() user: CurrentIamUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
    @UploadedFiles() files: Array<Express.Multer.File>,
  ): Promise<ProfileLicenseFileView[]> {
    return this.companiesService.addProfileLicenseFiles(
      user.id,
      profileId,
      files,
    );
  }

  @Post("company-profiles/:profileId/license/:fileId/replace")
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes("multipart/form-data")
  @ApiOperation({
    summary:
      "Replace a business-license file with a newly uploaded one (staged for " +
      "review on an approved company).",
  })
  async replaceProfileLicense(
    @CurrentUser() user: CurrentIamUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
    @Param("fileId", ParseUUIDPipe) fileId: string,
    @UploadedFiles() files: Array<Express.Multer.File>,
  ): Promise<ProfileLicenseFileView[]> {
    const file = files?.[0];
    if (!file) {
      throw new BadRequestException("A replacement file is required");
    }
    return this.companiesService.replaceProfileLicenseFile(
      user.id,
      profileId,
      fileId,
      file,
    );
  }

  @Delete("company-profiles/:profileId/license/:fileId")
  @ApiOperation({
    summary:
      "Remove a business-license file (staged for review on an approved company).",
  })
  async removeProfileLicense(
    @CurrentUser() user: CurrentIamUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
    @Param("fileId", ParseUUIDPipe) fileId: string,
  ): Promise<ProfileLicenseFileView[]> {
    return this.companiesService.removeProfileLicenseFile(
      user.id,
      profileId,
      fileId,
    );
  }

  @Get("company-profiles/:profileId/license")
  @ApiOperation({
    summary: "List business-license documents (with review state) for a profile",
  })
  async listProfileLicense(
    @CurrentUser() user: CurrentIamUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
  ): Promise<ProfileLicenseFileView[]> {
    return this.companiesService.listProfileLicenseFiles(user.id, profileId);
  }

  @Get("poa-delegation")
  @ApiOperation({
    summary:
      "List the Power of Attorney delegation letter (with review state) for the current user's company",
  })
  async listPoaDelegation(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<CompanyDocumentFileView[]> {
    return this.companiesService.listPoaDelegationFiles(user.id);
  }

  @Post("poa-delegation")
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes("multipart/form-data")
  @ApiOperation({
    summary:
      "Upload the Power of Attorney delegation letter, replacing any existing one. " +
      "For an approved company the upload is staged for backoffice review; during " +
      "onboarding it goes live.",
  })
  async uploadPoaDelegation(
    @CurrentUser() user: CurrentIamUser,
    @UploadedFiles() files: Array<Express.Multer.File>,
  ): Promise<CompanyDocumentFileView[]> {
    const file = files?.[0];
    if (!file) {
      throw new BadRequestException("A delegation letter file is required");
    }
    return this.companiesService.uploadPoaDelegationLetter(user.id, file);
  }

  @Delete("poa-delegation/:fileId")
  @ApiOperation({
    summary:
      "Remove the Power of Attorney delegation letter (staged for review on an approved company).",
  })
  async removePoaDelegation(
    @CurrentUser() user: CurrentIamUser,
    @Param("fileId", ParseUUIDPipe) fileId: string,
  ): Promise<CompanyDocumentFileView[]> {
    return this.companiesService.removePoaDelegationLetter(user.id, fileId);
  }

  @Patch("onboarding-step")
  @ApiOperation({ summary: "Persist the user's current onboarding wizard step" })
  @HttpCode(HttpStatus.NO_CONTENT)
  async setOnboardingStep(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: SetOnboardingStepDto,
  ): Promise<void> {
    await this.companiesService.setOnboardingStep(user.id, dto.step);
  }

  @Get("onboarding/requirements")
  @ApiOperation({
    summary:
      "What the current user's company still needs to finish onboarding (server-driven documents + outstanding items)",
  })
  async getOnboardingRequirements(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<OnboardingRequirementsResponseDto> {
    return this.companiesService.getOnboardingRequirements(user.id);
  }

  @Post("onboarding/complete")
  @ApiOperation({ summary: "Mark the current user's onboarding as complete" })
  async completeOnboarding(
    @CurrentUser() user: CurrentIamUser,
  ): Promise<CompanyInfoResponseDto> {
    const { profile, company } =
      await this.companiesService.markOnboardingComplete(user.id);
    return new CompanyInfoResponseDto(profile, company);
  }

  // Used by portal
  @Post("create")
  @ApiOperation({
    summary:
      "Create a company with its associated external profile (onboarding)",
  })
  async createWithProfile(
    @CurrentUser() user: CurrentIamUser,
    @Body() dto: CreateCompanyWithProfileDto,
  ): Promise<CompanyInfoResponseDto> {
    const nameParts = (user.name?.en ?? "").split(" ");
    const { profile, company } =
      await this.companiesService.createCompanyWithProfile(
        {
          userId: user.id,
          firstName: nameParts[0] || "",
          lastName: nameParts.slice(-1)[0] || "",
          email: user.email ?? "",
          phone: user.phoneNumber ?? "",
        },
        dto,
      );
    return new CompanyInfoResponseDto(profile, company);
  }

  // Used by backoffice
  @Post()
  @BookingStaff(FREIGHT_PERMS.customers.create)
  @ApiOperation({
    summary:
      "Create a new company (customer, freight_forwarder, dj_freight_forwarder, transporter)",
  })
  async create(@Body() dto: CreateCompanyDto): Promise<ResponseCompanyDto> {
    const company = await this.companiesService.createCompany(dto);
    return new ResponseCompanyDto(company);
  }

  @Get("stats")
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "Company counts by status (KPI strip)" })
  async getStats(): Promise<CompanyStatsResponseDto> {
    return this.companiesService.getCompanyStats();
  }

  @Get()
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "List companies (paginated, filterable)" })
  async findAll(
    @Query() query: ListCompaniesQueryDto,
  ): Promise<{ items: ResponseCompanyDto[]; total: number }> {
    const { items, total } = await this.companiesService.listCompanies(query);
    return { items: items.map((c) => new ResponseCompanyDto(c)), total };
  }

  @Get(":id")
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "Get company by ID" })
  async findById(
    @Param("id", ParseUUIDPipe) id: string,
  ): Promise<ResponseCompanyDto> {
    const company = await this.companiesService.findCompanyById(id);
    const dto = new ResponseCompanyDto(company);
    await this.populateLicenseFiles(company.id, dto.companyProfiles ?? []);
    return dto;
  }

  /**
   * Edits fields AND carries `status`, so it spans two authorities. The route
   * guard is one-of (a status-only caller must get in); the asserts below are
   * what actually authorize: touching `status` needs the permission
   * {@link STATUS_PERM} maps it to, touching anything else needs
   * `customers:update`. Both checks are required — without the second, a
   * caller holding only `customers:deactivate` could rename the company.
   */
  @Patch(":id")
  @BookingStaff([
    FREIGHT_PERMS.customers.update,
    FREIGHT_PERMS.customers.verify,
    FREIGHT_PERMS.customers.deactivate,
  ])
  @ApiOperation({ summary: "Update a company" })
  async update(
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: UpdateCompanyDto,
    @CurrentUser() user: TCurrentUser,
  ): Promise<ResponseCompanyDto> {
    const { status, ...fields } = dto;
    if (status) assertFreightPermission(user, STATUS_PERM[status]);
    if (Object.keys(fields).length > 0) {
      assertFreightPermission(user, FREIGHT_PERMS.customers.update);
    }
    const company = await this.companiesService.updateCompany(id, dto);
    return new ResponseCompanyDto(company);
  }

  @Delete(":id")
  @BookingStaff(FREIGHT_PERMS.customers.deactivate)
  @ApiOperation({ summary: "Soft-delete a company" })
  @HttpCode(HttpStatus.NO_CONTENT)
  async remove(@Param("id", ParseUUIDPipe) id: string): Promise<void> {
    await this.companiesService.deleteCompany(id);
  }

  /**
   * Dual-audience: staff read any customer's documents, and the portal reads
   * its OWN during onboarding (`companiesService.getDocuments`). So the route
   * is authenticated-only and the split happens here — same shape as
   * `GET /contracts/:id`. Gating it on a staff permission alone would 403 every
   * customer on their own documents.
   *
   * The staff arm is one-of because two pages consume it: the customer detail
   * page (`customers:view`) and the contract-request detail page, whose route
   * is gated on `contracts:view` — a contract reviewer without the customer
   * permission still needs the applicant's documents.
   */
  @Get(":companyId/documents")
  @UseGuards(JwtGuard)
  @ApiOperation({ summary: "List documents uploaded for a company" })
  async listDocuments(
    @Param("companyId", ParseUUIDPipe) companyId: string,
    @CurrentUser() user: TCurrentUser,
  ) {
    const isStaff = [
      FREIGHT_PERMS.customers.view,
      FREIGHT_PERMS.contracts.view,
      FREIGHT_PERMS.bookings.view,
    ].some((p) => hasFreightPermission(user, p));

    if (!isStaff) {
      const { company } = await this.companiesService.getCompanyInfoByUserId(
        user.id,
      );
      // Hidden as NotFound rather than Forbidden so company ids can't be probed.
      if (company.id !== companyId) {
        throw new NotFoundException(`Company ${companyId} not found`);
      }
    }
    const files = await this.filesService.findByResource(companyId, "companies");
    return Promise.all(
      files.map(async (f) => ({
        id: f.id,
        name: f.name,
        code: f.code,
        mimeType: f.mimeType,
        size: f.size,
        uploadedAt: f.createdAt,
        reviewStatus: f.reviewStatus,
        reviewNote: f.reviewNote,
        reviewedAt: f.reviewedAt,
        // Raw `f.url` is an un-signed MinIO path the browser can't open — sign
        // it so the file previews/downloads in the client.
        url: f.url ? await this.filesService.signUrl(f.url) : f.url,
      })),
    );
  }

  @Post("documents/:fileId/request-change")
  @BookingStaff(FREIGHT_PERMS.customers.verify)
  @ApiOperation({
    summary: "Ask the customer to correct one uploaded document",
    description:
      "Flags a single document with a reason the customer sees, notifies them, " +
      "and blocks role approval until they re-upload. Narrower than rejecting " +
      "the whole role.",
  })
  async requestDocumentChange(
    @CurrentUser() user: CurrentIamUser,
    @Param("fileId", ParseUUIDPipe) fileId: string,
    @Body() dto: RequestDocumentChangeDto,
  ) {
    const file = await this.companiesService.requestDocumentChange(
      fileId,
      dto.note,
      user.id,
    );
    return {
      id: file.id,
      name: file.name,
      code: file.code,
      reviewStatus: file.reviewStatus,
      reviewNote: file.reviewNote,
      reviewedAt: file.reviewedAt,
    };
  }

  @Post(":companyId/documents")
  @UseInterceptors(AnyFilesInterceptor())
  @ApiConsumes("multipart/form-data")
  @ApiOperation({ summary: "Upload documents for a company (onboarding)" })
  async uploadDocuments(
    @CurrentUser() user: CurrentIamUser,
    @Param("companyId", ParseUUIDPipe) companyId: string,
    @UploadedFiles() files: Array<Express.Multer.File>,
  ) {
    // Routed through the service so an approved company's uploads are staged for
    // review (and lock the customer), while onboarding uploads pass straight through.
    return this.companiesService.uploadCompanyDocuments(companyId, files, user.id);
  }

  /**
   * Approve / reject / suspend / blacklist all arrive here with the target in
   * the body, so authorization is per-status via {@link STATUS_PERM} rather
   * than on the route (the guard is only the one-of gate).
   */
  @Patch("company-profiles/:profileId/status")
  @BookingStaff([
    FREIGHT_PERMS.customers.verify,
    FREIGHT_PERMS.customers.deactivate,
  ])
  @ApiOperation({ summary: "Update a company profile's approval status" })
  async updateCompanyProfileStatus(
    @CurrentUser() user: TCurrentUser,
    @Param("profileId", ParseUUIDPipe) profileId: string,
    @Body() dto: UpdateCompanyProfileStatusDto,
  ): Promise<ResponseCompanyProfileDto> {
    assertFreightPermission(user, STATUS_PERM[dto.status]);
    const profile = await this.companiesService.setCompanyProfileStatus(
      profileId,
      dto.status,
      dto.note,
      user.id,
    );
    return new ResponseCompanyProfileDto(profile);
  }

  @Get(":companyId/change-requests")
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "List a company's profile change requests" })
  async listChangeRequests(
    @Param("companyId", ParseUUIDPipe) companyId: string,
  ): Promise<ChangeRequestResponseDto[]> {
    const requests = await this.companiesService.listChangeRequests(companyId);
    return requests.map((r) => new ChangeRequestResponseDto(r));
  }

  @Post("change-requests/:id/approve")
  @BookingStaff(FREIGHT_PERMS.customers.verify)
  @ApiOperation({
    summary: "Approve a pending profile change request (applies the changes)",
  })
  async approveChangeRequest(
    @CurrentUser() user: CurrentIamUser,
    @Param("id", ParseUUIDPipe) id: string,
  ): Promise<ChangeRequestResponseDto> {
    const request = await this.companiesService.approveChangeRequest(
      id,
      user.id,
    );
    return new ChangeRequestResponseDto(request);
  }

  @Post("change-requests/:id/reject")
  @BookingStaff(FREIGHT_PERMS.customers.verify)
  @ApiOperation({
    summary: "Reject a pending profile change request with a note",
  })
  async rejectChangeRequest(
    @CurrentUser() user: CurrentIamUser,
    @Param("id", ParseUUIDPipe) id: string,
    @Body() dto: RejectChangeRequestDto,
  ): Promise<ChangeRequestResponseDto> {
    const request = await this.companiesService.rejectChangeRequest(
      id,
      dto.note,
      user.id,
    );
    return new ChangeRequestResponseDto(request);
  }

  @Post(":companyId/profiles")
  @BookingStaff(FREIGHT_PERMS.customers.update)
  @ApiOperation({ summary: "Add a profile (employee) to a company" })
  async createProfile(
    @Param("companyId", ParseUUIDPipe) companyId: string,
    @Body() dto: CreateExternalProfileDto,
  ): Promise<ResponseExternalProfileDto> {
    const profile = await this.companiesService.createProfile({
      ...dto,
      companyId,
    });
    return new ResponseExternalProfileDto(profile);
  }

  @Get(":companyId/profiles")
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "List profiles for a company" })
  async listProfiles(
    @Param("companyId", ParseUUIDPipe) companyId: string,
  ): Promise<ResponseExternalProfileDto[]> {
    const profiles =
      await this.companiesService.findProfilesByCompany(companyId);
    return profiles.map((p) => new ResponseExternalProfileDto(p));
  }

  @Get("profile/user/:userId")
  @BookingStaff(FREIGHT_PERMS.customers.view)
  @ApiOperation({ summary: "Get profile by IAM user ID" })
  async findProfileByUser(
    @Param("userId", ParseUUIDPipe) userId: string,
  ): Promise<ResponseExternalProfileDto> {
    const profile = await this.companiesService.findProfileByUserId(userId);
    return new ResponseExternalProfileDto(profile);
  }
}
