import {
  BadRequestException,
  Body,
  Controller,
  ForbiddenException,
  HttpCode,
  HttpStatus,
  NotFoundException,
  Post,
  UseGuards,
} from '@nestjs/common';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
import { AuthUser } from '../../common/decorators/auth-user.decorator';
import { AuthWorkspace } from '../../common/decorators/auth-workspace.decorator';
import { User, Workspace } from '@docmost/db/types/entity.types';
import { PageRepo } from '@docmost/db/repos/page/page.repo';
import { PageAccessService } from '../page/page-access/page-access.service';
import { ShareService } from './share.service';
import { ShareAliasService } from './share-alias.service';
import {
  RemoveShareAliasDto,
  SetShareAliasDto,
  ShareAliasAvailabilityDto,
  ShareAliasForPageDto,
} from './dto/share-alias.dto';

/**
 * Authenticated management of vanity `/l/:alias` links. The PUBLIC resolve path
 * lives in `ShareAliasRedirectController` (`/l/:alias`); this controller only
 * creates/retargets/removes/looks-up aliases for editors.
 */
@UseGuards(JwtAuthGuard)
@Controller('share-aliases')
export class ShareAliasController {
  constructor(
    private readonly shareAliasService: ShareAliasService,
    private readonly shareService: ShareService,
    private readonly pageRepo: PageRepo,
    private readonly pageAccessService: PageAccessService,
  ) {}

  @HttpCode(HttpStatus.OK)
  @Post('set')
  async set(
    @Body() dto: SetShareAliasDto,
    @AuthUser() user: User,
    @AuthWorkspace() workspace: Workspace,
  ) {
    const page = await this.pageRepo.findById(dto.pageId);
    if (!page || page.workspaceId !== workspace.id) {
      throw new NotFoundException('Page not found');
    }

    // Editing the page is required to point an address at it.
    await this.pageAccessService.validateCanEdit(page, user);

    // The page must currently be publicly readable through the share graph; an
    // alias to a non-shared page would only ever 404.
    const resolved = await this.shareService.resolveReadableSharePage(
      undefined,
      page.id,
      workspace.id,
    );
    if (!resolved) {
      throw new BadRequestException('Page is not publicly shared');
    }

    const sharingAllowed = await this.shareService.isSharingAllowed(
      workspace.id,
      resolved.share.spaceId,
    );
    if (!sharingAllowed) {
      throw new ForbiddenException('Public sharing is disabled');
    }

    return this.shareAliasService.setAlias({
      workspaceId: workspace.id,
      pageId: page.id,
      creatorId: user.id,
      alias: dto.alias,
      confirmReassign: dto.confirmReassign,
    });
  }

  @HttpCode(HttpStatus.OK)
  @Post('remove')
  async remove(
    @Body() dto: RemoveShareAliasDto,
    @AuthUser() user: User,
    @AuthWorkspace() workspace: Workspace,
  ) {
    const alias = await this.shareAliasService.getAliasById(
      dto.aliasId,
      workspace.id,
    );
    if (!alias) {
      throw new NotFoundException('Alias not found');
    }

    // Only someone who can edit the (current) target page may free the address.
    // A dangling alias (page deleted) can be removed by any workspace member.
    if (alias.pageId) {
      const page = await this.pageRepo.findById(alias.pageId);
      if (page) {
        await this.pageAccessService.validateCanEdit(page, user);
      }
    }

    await this.shareAliasService.removeAlias(alias.id, workspace.id);
  }

  @HttpCode(HttpStatus.OK)
  @Post('availability')
  async availability(
    @Body() dto: ShareAliasAvailabilityDto,
    @AuthWorkspace() workspace: Workspace,
  ) {
    return this.shareAliasService.checkAvailability(dto.alias, workspace.id);
  }

  @HttpCode(HttpStatus.OK)
  @Post('for-page')
  async forPage(
    @Body() dto: ShareAliasForPageDto,
    @AuthUser() user: User,
    @AuthWorkspace() workspace: Workspace,
  ) {
    const page = await this.pageRepo.findById(dto.pageId);
    if (!page || page.workspaceId !== workspace.id) {
      throw new NotFoundException('Page not found');
    }
    await this.pageAccessService.validateCanView(page, user);

    return (
      (await this.shareAliasService.getAliasForPage(page.id, workspace.id)) ??
      null
    );
  }
}
