import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Query,
  UseGuards,
  UseInterceptors,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { AssetService } from './asset.service';
import {
  ApiBearerAuth,
  ApiOkResponse,
  ApiOperation,
  ApiTags,
} from '@nestjs/swagger';
import {
  AssetShortDTO,
  AssetViewDTO,
  AssetWhereRequestDTO,
} from './dto/asset-dto';
import {
  AssetChangeDTO,
  AssetCreateDTO,
  AssetDeleteResultDTO,
} from './dto/asset-change-dto';
import {
  ApiOkResponseListWithMore,
  ApiOkResponseListWithTotal,
  ApiResultListWithMore,
  ApiResultListWithTotal,
} from 'src/common/types/api-result';
import {
  ProjectAccess,
  ProjectAccessInterceptor,
  RequestProjectAccess,
} from 'src/utils/project-access';
import { UserDTO } from 'src/common/dto/user-dto';
import { RequestUser } from 'src/common/request-user';
import { AssetsFullResultDTO } from './dto/assets-full-result-dto';
import {
  AssetQueryDTO,
  AssetViewQueryDTO,
  AssetViewQueryPostDTO,
} from './dto/asset-query-dto';
import { ApiError } from '../common/error/api-error';
import { ApiErrorCodes } from '../common/error/api-error-codes';
import { AssetsGraphResultDTO } from './dto/assets-graph-result-dto';
import {
  AssetRefCreateDTO,
  AssetsRefResultDTO,
} from './dto/asset-reference-dto';
import { RefService } from './ref.service';
import { OptTokenOrJwtAuthGuard } from '../guards/opt-token-or-jwt.auth.guard';
import { AssetReorderDTO } from './dto/asset-reorder-dto';
import {
  CommentReplyChangeDTO,
  CommentCreateDTO,
  CommentCreateResponseDTO,
  GetCommentsParamsDTO,
  CommentReplyDTO,
  GetCommentsResultDTO,
} from './dto/comment-dto';
import { CommentService } from './comment.service';
import { AssetHistoryDTO, AssetHistoryQueryDTO } from './dto/history-dto';
import {
  convertAssetQueryToPostFormat,
  convertAssetQueryToSelection,
  convertAssetQueryToSelectionBase,
  convertAssetWhereRequestToWhere,
} from './asset-utils';

@ApiTags('asset')
@Controller('assets')
@UseGuards(OptTokenOrJwtAuthGuard)
@UseInterceptors(ProjectAccessInterceptor)
@ApiBearerAuth()
export class AssetController {
  constructor(
    private readonly assetService: AssetService,
    private readonly refService: RefService,
    private readonly commentService: CommentService,
  ) {}

  @Get('short')
  @ApiOperation({
    summary: 'Get short asset list',
  })
  @ApiOkResponseListWithTotal(AssetShortDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getAssetsShort(
    @Query() filterData: AssetQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ApiResultListWithTotal<AssetShortDTO>> {
    const selection = convertAssetQueryToSelectionBase(filterData);
    return await this.assetService.getAssetsShort(selection, projectAccess);
  }

  @Get('full')
  @ApiOperation({
    summary: 'Get full asset list',
  })
  @ApiOkResponse({
    type: AssetsFullResultDTO,
  })
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getAssetsFull(
    @Query() filterData: AssetQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsFullResultDTO> {
    const selection = convertAssetQueryToSelectionBase(filterData);
    return await this.assetService.getAssetsFull(
      selection,
      projectAccess,
      true,
    );
  }

  @Get('view')
  @ApiOperation({
    summary: 'Get view asset list',
  })
  @ApiOkResponseListWithTotal(AssetViewDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getAssetsView(
    @Query() filterData: AssetViewQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ApiResultListWithTotal<AssetViewDTO>> {
    const filterDataPost = convertAssetQueryToPostFormat(filterData);
    const selection = convertAssetQueryToSelection(filterDataPost);
    return await this.assetService.getAssetsView(selection, projectAccess);
  }

  @Post('view/get')
  @ApiOperation({
    summary: 'Get view asset list',
  })
  @ApiOkResponseListWithTotal(AssetViewDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getAssetsViewPost(
    @Body() filterData: AssetViewQueryPostDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ApiResultListWithTotal<AssetViewDTO>> {
    const selection = convertAssetQueryToSelection(filterData);
    return await this.assetService.getAssetsView(selection, projectAccess);
  }

  @Get('graph')
  @ApiOperation({
    summary: 'Get asset list for graph',
  })
  @ApiOkResponse({
    type: AssetsGraphResultDTO,
  })
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getAssetsGraph(
    @Query() filterData: AssetQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsGraphResultDTO> {
    const selection = convertAssetQueryToSelectionBase(filterData);
    return await this.assetService.getAssetsGraph(selection, projectAccess);
  }

  @Post('create')
  @ApiOperation({
    summary: 'Create asset',
  })
  @ApiOkResponse({
    type: AssetsFullResultDTO,
  })
  async createAsset(
    @RequestUser() account: UserDTO,
    @Body() params: AssetCreateDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsFullResultDTO> {
    if (!params.main) {
      throw new ApiError(
        'Asset main data is not set',
        ApiErrorCodes.PARAM_BAD_VALUE,
      );
    }
    const new_id = await this.assetService.createAsset(params, projectAccess);

    return await this.assetService.getAssetsFull(
      {
        where: {
          id: new_id,
        },
      },
      projectAccess,
      false,
    );
  }

  @Post('change')
  @ApiOperation({
    summary: 'Change assets',
  })
  @ApiOkResponse({
    type: AssetsFullResultDTO,
  })
  async changeAsset(
    @RequestUser() account: UserDTO,
    @Body() params: AssetChangeDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsFullResultDTO> {
    const where = convertAssetWhereRequestToWhere(params);
    const changed_ids = await this.assetService.changeAssets(
      where,
      params,
      projectAccess,
    );

    return await this.assetService.getAssetsFull(
      {
        where: {
          id: changed_ids,
        },
      },
      projectAccess,
      false,
    );
  }

  @Post('delete')
  @ApiOperation({
    summary: 'Delete assets',
  })
  @ApiOkResponse({
    type: AssetDeleteResultDTO,
  })
  async deleteAssets(
    @Body() params: AssetWhereRequestDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetDeleteResultDTO> {
    const where = convertAssetWhereRequestToWhere(params);
    const ids = await this.assetService.deleteAssets(where, projectAccess);
    return { ids };
  }

  @Post('restore')
  @ApiOperation({
    summary: 'Restore assets',
  })
  @ApiOkResponse({
    type: AssetsFullResultDTO,
  })
  async restoreAssets(
    @Body() params: AssetWhereRequestDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsFullResultDTO> {
    const where = convertAssetWhereRequestToWhere(params);
    const asset_ids = await this.assetService.restoreAssets(
      where,
      projectAccess,
    );

    return await this.assetService.getAssetsFull(
      {
        where: {
          id: asset_ids,
        },
      },
      projectAccess,
      false,
    );
  }

  @Get('ref')
  @ApiOperation({
    summary: 'Get refs',
  })
  @ApiOkResponse({
    type: AssetsRefResultDTO,
  })
  async getRefs(
    @Query() filterData: AssetQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsRefResultDTO> {
    const selection = convertAssetQueryToSelectionBase(filterData);
    const res = await this.refService.getRefs(selection, projectAccess);
    return res;
  }

  @Post('ref')
  @ApiOperation({
    summary: 'Create refs',
  })
  @ApiOkResponse({
    type: AssetsRefResultDTO,
  })
  async createRef(
    @Body() params: AssetRefCreateDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetsRefResultDTO> {
    const where = convertAssetWhereRequestToWhere(params);
    const res = await this.refService.createRefs(
      where,
      params.blockRef ?? null,
      params.targetAssetId,
      params.targetBlockRef,
      projectAccess,
    );
    return res;
  }

  @Delete('ref')
  @ApiOperation({
    summary: 'Delete refs',
  })
  @ApiOkResponse({
    type: AssetDeleteResultDTO,
  })
  async deleteRef(
    @Body() params: AssetRefCreateDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<AssetDeleteResultDTO> {
    const where = convertAssetWhereRequestToWhere(params);
    const res = await this.refService.deleteRef(
      where,
      params.blockRef ?? null,
      params.targetAssetId,
      params.targetBlockRef,
      projectAccess,
    );
    return {
      ids: res,
    };
  }

  @Post('reorder')
  @ApiOperation({
    summary: 'Reorder assets',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async reorderColumns(
    @Body() params: AssetReorderDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<{ ids: string[] }> {
    return {
      ids: await this.assetService.reorder(params, projectAccess),
    };
  }

  @Post('comment')
  @ApiOperation({
    summary: 'Create comment',
  })
  @ApiOkResponse({
    type: CommentCreateResponseDTO,
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async createComment(
    @Body() params: CommentCreateDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<CommentCreateResponseDTO> {
    return this.commentService.createComment(params, projectAccess);
  }

  @Post('comment/:comment_id/reply')
  @ApiOperation({
    summary: 'Add answer',
  })
  @ApiOkResponse({
    type: CommentReplyDTO,
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async addAnswer(
    @Body() params: CommentReplyChangeDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Param('comment_id') comment_id: string,
  ): Promise<CommentReplyDTO> {
    return this.commentService.addAnswer(params, comment_id, projectAccess);
  }

  @Patch('comment/:comment_id/reply/:reply_id')
  @ApiOperation({
    summary: 'Change answer',
  })
  @ApiOkResponse({
    type: CommentReplyDTO,
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async changeReply(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Param('comment_id') comment_id: string,
    @Param('reply_id') reply_id: string,
    @Body() params: CommentReplyChangeDTO,
    @RequestUser() user: UserDTO,
  ): Promise<CommentReplyDTO> {
    return await this.commentService.changeAnswer(
      projectAccess,
      comment_id,
      reply_id,
      params,
      user,
    );
  }

  @Delete('comment/:comment_id/reply/:reply_id')
  @ApiOperation({
    summary: 'Delete comment',
  })
  async deleteReply(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Param('comment_id') comment_id: string,
    @Param('reply_id') reply_id: string,
    @RequestUser() user: UserDTO,
  ) {
    await this.commentService.deleteAnswer(
      projectAccess,
      comment_id,
      reply_id,
      user,
    );
  }

  @Get('comment/:comment_id/reply')
  @ApiOperation({
    summary: 'Get comments',
  })
  @ApiOkResponse({
    type: GetCommentsResultDTO,
  })
  async getComments(
    @Param('comment_id') comment_id: string,
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Query() params: GetCommentsParamsDTO,
  ): Promise<GetCommentsResultDTO> {
    return await this.commentService.getComments(
      projectAccess,
      comment_id,
      parseInt(params.count),
      parseInt(params.offset),
    );
  }

  @Get('history/:asset_id')
  @ApiOperation({
    summary: 'Get history',
  })
  @ApiOkResponseListWithMore(AssetHistoryDTO)
  async getHistory(
    @Param('asset_id') asset_id: string,
    @Query() filterData: AssetHistoryQueryDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ApiResultListWithMore<AssetHistoryDTO>> {
    return await this.assetService.getHistory(
      projectAccess,
      asset_id,
      filterData,
    );
  }
}
