import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  Query,
  Res,
  StreamableFile,
  UploadedFile,
  UseGuards,
  UseInterceptors,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { ProjectService } from './project.service';
import {
  ApiBearerAuth,
  ApiBody,
  ApiConsumes,
  ApiOkResponse,
  ApiOperation,
  ApiTags,
} from '@nestjs/swagger';
import {
  AcceptInvitationDTO,
  InvitationDTO,
  InvitationQueryDTO,
  RevokeInvitationDTO,
  SendInvitationDTO,
} from './dto/invitation-dto';
import {
  ApiOkResponseListWithTotal,
  ApiResultListWithTotal,
} from 'src/common/types/api-result';
import {
  ChangeMemberDTO,
  ExportProjectDTO,
  MemberDTO,
  MemberQueryDTO,
  ProjectInfoDTO,
} from './dto/project-dto';
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 { ApiError } from '../common/error/api-error';
import { ApiErrorCodes } from '../common/error/api-error-codes';
import {
  ProjectStatsByViewsAndVisitorsDTO,
  ProjectStatsQueryDTO,
} from './dto/stats-dto';
import { UserRoleDTO, UserRoleQueryDTO } from '../user/dto/user-role-dto';
import { SendMessageDTO, SendMessageResultDTO } from './dto/message-dto';
import { OptTokenOrJwtAuthGuard } from '../guards/opt-token-or-jwt.auth.guard';
import { Response, Express } from 'express';
import * as dayjs from 'dayjs';
import { ProjectImportResponseDTO } from './dto/project-import.dto';
import { ProjectImportService } from './project-import.service';
import { FileInterceptor } from '@nestjs/platform-express';
import { ProjectExportService } from './project-export.service';

@ApiTags('project')
@Controller('project')
@UseGuards(OptTokenOrJwtAuthGuard)
@UseInterceptors(ProjectAccessInterceptor)
@ApiBearerAuth()
export class ProjectController {
  constructor(
    private readonly projectService: ProjectService,
    private readonly projectImportService: ProjectImportService,
    private readonly projectExportService: ProjectExportService,
  ) {}

  @Get('info')
  @ApiOperation({
    summary: 'Get project info',
  })
  @ApiOkResponse({
    type: ProjectInfoDTO,
  })
  async getProjectInfo(
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ProjectInfoDTO> {
    return await this.projectService.getProjectInfo(projectAccess.projectId);
  }

  @Get('members')
  @ApiOperation({
    summary: 'Get project members',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  @ApiOkResponseListWithTotal(MemberDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getMembers(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Query() filterData: MemberQueryDTO,
  ): Promise<ApiResultListWithTotal<MemberDTO>> {
    return await this.projectService.getMembers(filterData, projectAccess);
  }

  @Get('roles')
  @ApiOperation({
    summary: 'Get project roles',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  @ApiOkResponseListWithTotal(UserRoleDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getRoles(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Query() filterData: UserRoleQueryDTO,
  ): Promise<ApiResultListWithTotal<UserRoleDTO>> {
    return await this.projectService.getRoles(filterData, projectAccess);
  }

  @Patch('members/:user_id')
  @ApiOperation({
    summary: 'Change role, name',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async changeMember(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Param('user_id') user_id: number,
    @Body() params: ChangeMemberDTO,
  ): Promise<any> {
    return await this.projectService.changeMember(
      user_id,
      params,
      projectAccess,
    );
  }

  @Delete('members/:user_id')
  @ApiOperation({
    summary: 'Delete members',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async deleteMember(
    @RequestUser() account: UserDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Param('user_id') user_id: number,
  ): Promise<void> {
    return await this.projectService.exitFromProject(user_id, projectAccess);
  }

  @Get('invitations')
  @ApiOperation({
    summary: 'Get project invitations',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  @ApiOkResponseListWithTotal(InvitationDTO)
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getInvitations(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Query() filterData: InvitationQueryDTO,
  ): Promise<ApiResultListWithTotal<InvitationDTO>> {
    return await this.projectService.getInvitations(filterData, projectAccess);
  }

  @Post('invitations/send')
  @ApiOperation({
    summary: 'Send invitation',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async sendInvitation(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Body() params: SendInvitationDTO,
  ): Promise<InvitationDTO> {
    return await this.projectService.sendInvitation(params, projectAccess);
  }

  @Post('invitations/accept')
  @ApiOperation({
    summary: 'Accept invitation',
  })
  async acceptInvitation(
    @RequestUser() account: UserDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Body() params: AcceptInvitationDTO,
  ): Promise<boolean> {
    if (!account) {
      throw new ApiError('You must be logged in', ApiErrorCodes.ACCESS_DENIED);
    }
    return await this.projectService.acceptInvitation(
      account,
      projectAccess.projectId,
      params,
    );
  }

  @Post('invitations/reject')
  @ApiOperation({
    summary: 'Accept invitation',
  })
  async rejectInvitation(
    @RequestUser() account: UserDTO,
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Body() params: AcceptInvitationDTO,
  ): Promise<boolean> {
    if (!account) {
      throw new ApiError('You must be logged in', ApiErrorCodes.ACCESS_DENIED);
    }
    return await this.projectService.rejectInvitation(
      account,
      projectAccess.projectId,
      params.code,
    );
  }

  @Post('invitations/revoke')
  @ApiOperation({
    summary: 'Revoke invitation',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  async revokeInvitation(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Body() params: RevokeInvitationDTO,
  ): Promise<void> {
    return await this.projectService.revokeInvitation(
      params.email,
      projectAccess,
    );
  }

  @Get('stats')
  @ApiOperation({
    summary: 'Stats',
  })
  @UseInterceptors(ProjectAccessInterceptor)
  @ApiOkResponse({
    type: ProjectStatsByViewsAndVisitorsDTO,
  })
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async getStats(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Query() filterData: ProjectStatsQueryDTO,
  ): Promise<ProjectStatsByViewsAndVisitorsDTO> {
    return await this.projectService.getStats(filterData, projectAccess);
  }

  @Post('sendMessage')
  @ApiOperation({
    summary: 'Write notification',
  })
  @ApiOkResponse({
    type: SendMessageResultDTO,
  })
  @UsePipes(
    new ValidationPipe({
      transform: true,
      transformOptions: { enableImplicitConversion: true },
    }),
  )
  async sendMessage(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Body() body: SendMessageDTO,
  ): Promise<SendMessageResultDTO> {
    return await this.projectService.sendMessage(body, projectAccess.projectId);
  }

  @Get('export')
  @ApiOperation({
    summary: 'Get project info',
  })
  @ApiOkResponse({
    type: ProjectInfoDTO,
  })
  async exportProject(
    @RequestProjectAccess() projectAccess: ProjectAccess,
    @Res({ passthrough: true }) response: Response,
    @Query() filterData: ExportProjectDTO,
  ): Promise<any> {
    const buffer = await this.projectExportService.exportProject(
      filterData,
      projectAccess,
    );
    const today = dayjs(new Date()).format('YYYY_MM_DD');
    const file_title = `${projectAccess.projectId}_${today}.ims-project`;
    const type = 'application/octet-stream';
    response.setHeader('Content-Type', type);
    const encoded = encodeURIComponent(file_title);
    response.setHeader(
      'Content-Disposition',
      `attachment; filename*=UTF-8''${encoded}; filename="${encoded}"`,
    );
    return new StreamableFile(buffer);
  }

  @Post('import')
  @ApiOperation({
    summary: 'Импорт проекта',
  })
  @ApiOkResponse({
    description: '',
    type: ProjectImportResponseDTO,
  })
  @UseInterceptors(FileInterceptor('file'))
  @ApiConsumes('multipart/form-data')
  @ApiBody({
    required: true,
    schema: {
      type: 'object',
      required: ['file'],
      properties: {
        file: {
          type: 'string',
          format: 'binary',
        },
      },
    },
  })
  async importProject(
    @UploadedFile() file: Express.Multer.File,
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<ProjectImportResponseDTO> {
    const res = await this.projectImportService.importProject(
      projectAccess,
      file,
    );
    return res;
  }

  @Delete('clearAll')
  @ApiOperation({
    summary: 'Clear all data from project',
  })
  async clearAllInProject(
    @RequestProjectAccess() projectAccess: ProjectAccess,
  ): Promise<any> {
    await this.projectService.clearAllDataInProject(projectAccess);
  }
}
