import {
  ACAttrFilter,
  ACGuard,
  ApiPaginatedResponse,
  diskStorage,
  PageDto,
  PageMetaDto,
  PageOptionsDto,
  pick,
  UserType,
} from '@gadmin2n/nest-common';
import {
  Body,
  Controller,
  Delete,
  Inject,
  NotFoundException,
  Param,
  ParseIntPipe,
  Patch,
  Post,
  Req,
  UploadedFile,
  UseGuards,
  UseInterceptors,
} from '@nestjs/common';
import {
  ApiCreatedResponse,
  ApiNotFoundResponse,
  ApiOkResponse,
  ApiTags,
} from '@nestjs/swagger';
import { Prisma } from '@prisma/client';
import { UseRoles } from 'nest-access-control';
import { WINSTON_MODULE_PROVIDER } from 'nest-winston';
import { Logger } from 'winston';
import { CreatePageDto } from '../../generated/page/dto/create-page.dto';
import {
  EffectedCount,
  PageFindManyArgs,
  PageWhereSelect,
  UploudResponse,
} from '../../generated/page/dto/page-types.dto';
import { UpdatePageDto } from '../../generated/page/dto/update-page.dto';
import { PageEntity } from '../../generated/page/entities/page.entity';
import { PageService } from './page.service';

import { ConfigService } from '@nestjs/config';
import { FileInterceptor } from '@nestjs/platform-express';
import { Type } from 'class-transformer';
import { IsArray, IsOptional, ValidateNested } from 'class-validator';

// 资源分配输入类型
export class ResourceAssignmentInput {
  resourceId: number;
  resourceName: string;
  assignedPermissions: string[];
}

// 自定义控制器层输入类型，扩展 resourceAssignments 字段
export class UpdatePageWithResourcesDto extends UpdatePageDto {
  @IsOptional()
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => ResourceAssignmentInput)
  resourceAssignments?: ResourceAssignmentInput[];
}

// 创建页面时支持 resourceAssignments
export class CreatePageWithResourcesDto extends CreatePageDto {
  @IsOptional()
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => ResourceAssignmentInput)
  resourceAssignments?: ResourceAssignmentInput[];
}

@UseGuards(ACGuard)
@Controller('page')
@ApiTags('page')
export class PageController {
  constructor(
    private readonly pageService: PageService,
    private readonly config: ConfigService,
    @Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger,
  ) {}

  @Post('createOne')
  @UseRoles({
    resource: 'page',
    action: 'create',
    possession: 'any',
  })
  @ApiCreatedResponse({ type: PageEntity })
  async createOne(
    @Body() body: CreatePageWithResourcesDto,
    @ACAttrFilter() acAttrFilter,
    @Req() request: Request & { user: UserType },
  ) {
    const { resourceAssignments, ...pageData } = body;
    pageData.creator = request.user.username;
    // 删除 createdAt 字段，防止被更新
    delete (pageData as any).createdAt;

    // 转换 resourceAssignments 为 service 层需要的格式
    const resourceIds = resourceAssignments?.map((item) => ({
      resourceId: item.resourceId,
      actions: item.assignedPermissions,
    }));

    // 使用事务创建 page 及其资源关系
    const item = await this.pageService.createPageWithResources(
      acAttrFilter(pageData),
      resourceIds,
    );

    return new PageEntity(acAttrFilter(item));
  }

  @Post('findMany')
  @UseRoles({
    resource: 'page',
    action: 'read',
    possession: 'any',
  })
  @ApiPaginatedResponse(PageEntity)
  async findMany(
    @Body() body: PageFindManyArgs,
    @ACAttrFilter() acAttrFilter,
  ): Promise<PageDto<PageEntity>> {
    const { itemCount, entities } = await this.pageService.findMany(
      body as Prisma.PageFindManyArgs,
    );
    const { skip = 0, take = 10 } = body as Prisma.PageFindManyArgs;
    const pageOptionsDto: PageOptionsDto = { skip, take };
    const pageMetaDto = new PageMetaDto({ itemCount, pageOptionsDto });

    return new PageDto(
      entities.map((item) => new PageEntity(acAttrFilter(item))),
      pageMetaDto,
    );
  }

  @Post('/findUnique/:id')
  @UseRoles({
    resource: 'page',
    action: 'read',
    possession: 'any',
  })
  @ApiOkResponse({ type: PageEntity })
  @ApiNotFoundResponse({ description: 'Cannot found this record.' })
  async findUnique(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: PageWhereSelect,
    @ACAttrFilter() acAttrFilter,
  ) {
    const item = await this.pageService.findUnique(id, body);
    if (!item) {
      throw new NotFoundException();
    }
    return new PageEntity(acAttrFilter(item));
  }

  @Patch('updateUnique/:id')
  @UseRoles({
    resource: 'page',
    action: 'update',
    possession: 'any',
  })
  @ApiOkResponse({ type: PageEntity })
  @ApiNotFoundResponse({ description: 'Cannot found this record.' })
  async updateUnique(
    @Param('id', ParseIntPipe) id: number,
    @Body() body: UpdatePageWithResourcesDto,
    @ACAttrFilter() acAttrFilter,
  ) {
    const { resourceAssignments, ...pageData } = body;

    // 删除 createdAt 字段，防止被更新
    delete (pageData as any).createdAt;

    // 转换 resourceAssignments 为 service 层需要的格式
    const resourceIds = resourceAssignments?.map((item) => ({
      resourceId: item.resourceId,
      actions: item.assignedPermissions,
    }));
    //使用 Prisma 事务确保原子性
    const item = await this.pageService.updatePageWithResources(
      id,
      acAttrFilter(pageData),
      resourceIds,
    );

    return new PageEntity(acAttrFilter(item));
  }

  @Delete('deleteUnique/:id')
  @UseRoles({
    resource: 'page',
    action: 'delete',
    possession: 'any',
  })
  @ApiOkResponse({ type: EffectedCount })
  @ApiNotFoundResponse({ description: 'Cannot found this record.' })
  async deleteUnique(@Param('id', ParseIntPipe) id: number) {
    // 检查页面是否存在
    const page = await this.pageService.findUnique(id, { id: true });
    if (!page) {
      throw new NotFoundException('页面不存在');
    }

    // 检查页面是否有子页面（children）
    const childPagesCount = await this.pageService.count({
      where: { parentId: id },
    });

    if (childPagesCount > 0) {
      throw new Error(
        '该页面是父页面，存在子页面，无法删除。请先删除所有子页面后再删除此页面。',
      );
    }

    const res = await this.pageService.deletePageWithResources(id);
    if (!res.count) {
      throw new NotFoundException();
    }
    return res;
  }

  @Post('upload')
  @UseRoles({
    resource: 'page',
    action: 'create',
    possession: 'any',
  })
  @ApiCreatedResponse({ type: UploudResponse })
  @UseInterceptors(FileInterceptor('file'))
  async uploadFile(@UploadedFile() file: Express.Multer.File) {
    const fileName = await diskStorage(
      file,
      this.config.get('media')['rootDir'] + '/page/',
    );

    return {
      ...pick(file, ['mimetype', 'originalname', 'size']),
      url: this.config.get('media')['address'] + '/page/' + fileName,
      name: fileName,
    };
  }
}
