import {
  Body,
  Controller,
  Delete,
  Get,
  HttpException,
  HttpStatus,
  Logger,
  Param,
  Post,
  Put,
  UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger';
import { validate } from 'class-validator';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { NewsletterSubscription } from '../newsletter/newsletter-subscription.schema';
import { NewsletterService } from '../newsletter/newsletter.service';
import { Structure } from '../structures/schemas/structure.schema';
import { StructuresService } from '../structures/services/structures.service';
import { Roles } from '../users/decorators/roles.decorator';
import { RolesGuard } from '../users/guards/roles.guard';
import { IUser } from '../users/interfaces/user.interface';
import { EmployerService } from '../users/services/employer.service';
import { JobsService } from '../users/services/jobs.service';
import { UsersService } from '../users/services/users.service';
import { AdminService } from './admin.service';
import { PendingStructureDto } from './dto/pending-structure.dto';
import { SetUserEmployerDto } from './dto/set-user-employer.dto';
import { SetUserJobDto } from './dto/set-user-job.dto';

@ApiTags('admin')
@Controller('admin')
export class AdminController {
  private readonly logger = new Logger(AdminController.name);
  constructor(
    private usersService: UsersService,
    private structuresService: StructuresService,
    private jobsService: JobsService,
    private employerService: EmployerService,
    private newsletterService: NewsletterService,
    private adminService: AdminService
  ) {}

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @Get('pendingStructures')
  @ApiOperation({ description: 'Get pending structures for validation' })
  public async getPendingAttachments(): Promise<PendingStructureDto[]> {
    const pendingStructure = await this.usersService.getPendingStructures();
    return Promise.all(
      pendingStructure.map(async (structure) => {
        const structureDocument = await this.structuresService.findOne(structure.structureId);
        structure.structureName = structureDocument.structureName;
        structure.updatedAt = structureDocument.updatedAt;
        return structure;
      })
    );
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @Get('adminStructuresList')
  @ApiOperation({ description: 'Get pending structures for validation' })
  public async getAdminStructuresList() {
    this.logger.debug('getAdminStructuresList');
    const structuresList = { claimed: [], inClaim: [], toClaim: [], incomplete: [] };
    const inClaimStructures = await this.getPendingAttachments();
    structuresList.inClaim = inClaimStructures.map((structure) => {
      const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
      return {
        structureId: structure.structureId,
        structureName: structure.structureName,
        updatedAt: lastUpdateDate,
        isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
      };
    });
    const toClaimStructures = await this.structuresService.findAllUnclaimed();
    structuresList.toClaim = toClaimStructures
      .filter((demand) => !structuresList.inClaim.find((elem) => elem.structureId == demand.structureId))
      .map((structure) => {
        const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
        return {
          structureId: structure.structureId,
          structureName: structure.structureName,
          updatedAt: lastUpdateDate,
          isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
        };
      });
    const allStructures = await this.structuresService.findAll();
    structuresList.claimed = allStructures
      .filter(
        (demand) =>
          !structuresList.inClaim.find((elem) => elem.structureId == demand.id) &&
          !structuresList.toClaim.find((elem) => elem.structureId == demand.id)
      )
      .map((structure) => {
        const lastUpdateDate = this.adminService.getLastUpdateDate(structure);
        return {
          structureId: structure.id,
          structureName: structure.structureName,
          updatedAt: lastUpdateDate,
          isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
        };
      });
    structuresList.incomplete = await Promise.all(
      allStructures.map(async (struct) => {
        const validity = await validate(new Structure(struct));
        if (validity.length > 0) {
          this.logger.debug(`getAdminStructuresList - validation failed. errors: ${validity.toString()}`);
          const lastUpdateDate = this.adminService.getLastUpdateDate(struct);
          return {
            structureId: struct.id,
            structureName: struct.structureName,
            updatedAt: lastUpdateDate,
            isOutdated: this.adminService.isDateOutdated(lastUpdateDate, 6),
          };
        } else {
          this.logger.debug('getAdminStructuresList - validation succeed');
          return null;
        }
      })
    );
    structuresList.incomplete = structuresList.incomplete.filter((structure) => structure);
    structuresList.claimed.sort((a, b) => a.structureName.localeCompare(b.structureName));
    structuresList.inClaim.sort((a, b) => a.structureName.localeCompare(b.structureName));
    structuresList.toClaim.sort((a, b) => a.structureName.localeCompare(b.structureName));
    structuresList.incomplete.sort((a, b) => a.structureName.localeCompare(b.structureName));
    return structuresList;
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @Post('validatePendingStructure')
  @ApiOperation({ description: 'Validate structure ownership' })
  public async validatePendingStructure(@Body() pendingStructureDto: PendingStructureDto) {
    const structure = await this.structuresService.findOne(pendingStructureDto.structureId.toString());
    if (!structure || structure.deletedAt) {
      throw new HttpException('Structure does not exist', HttpStatus.NOT_FOUND);
    }
    await this.usersService.validatePendingStructure(
      pendingStructureDto.userEmail,
      pendingStructureDto.structureId.toString(),
      structure.structureName,
      true
    );
    const pendingStructure = await this.usersService.getPendingStructures();
    return Promise.all(
      pendingStructure.map(async (pendingStructureData) => {
        pendingStructureData.structureName = (
          await this.structuresService.findOne(pendingStructureData.structureId)
        ).structureName;
        return pendingStructureData;
      })
    );
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @Post('rejectPendingStructure')
  @ApiOperation({ description: 'Refuse structure ownership' })
  public async refusePendingStructure(@Body() pendingStructureDto: PendingStructureDto) {
    const structure = await this.structuresService.findOne(pendingStructureDto.structureId.toString());
    if (!structure || structure.deletedAt) {
      throw new HttpException('Structure does not exist', HttpStatus.NOT_FOUND);
    }
    await this.usersService.validatePendingStructure(
      pendingStructureDto.userEmail,
      pendingStructureDto.structureId.toString(),
      structure.structureName,
      false
    );
    const pendingStructure = await this.usersService.getPendingStructures();
    return Promise.all(
      pendingStructure.map(async (pendingStructureData) => {
        pendingStructureData.structureName = (
          await this.structuresService.findOne(pendingStructureData.structureId)
        ).structureName;
        return pendingStructureData;
      })
    );
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @Delete('user/:id')
  @ApiBearerAuth('JWT')
  @ApiParam({ name: 'id', type: String, required: true })
  public async deleteUser(@Param() params) {
    const user = await this.usersService.deleteOneId(params.id);
    user.structuresLink.forEach((structureId) => {
      this.usersService.isStructureClaimed(structureId.toString()).then(async (userFound) => {
        if (!userFound) {
          const structure = await this.structuresService.findOne(structureId.toString());
          this.structuresService.deleteOne(structure);
        }
      });
    });
    return user;
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @Post('searchUsers')
  public async searchUsers(@Body() searchString: { searchString: string }) {
    if (searchString && searchString.searchString && searchString.searchString.length > 0)
      return this.usersService.searchUsers(searchString.searchString);
    else return this.usersService.findAll();
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @Get('unAttachedUsers')
  public async findUnattachedUsers() {
    return this.usersService.findAllUnattached().then((formatUsers) => {
      return formatUsers.map((user) => {
        return {
          id: user._id,
          createdAt: user.createdAt ? user.createdAt.toLocaleDateString('fr-FR') : '',
          unattachedSince: user.unattachedSince ? user.unattachedSince.toLocaleDateString('fr-FR') : '',
          surname: user.surname,
          name: user.name,
          email: user.email,
          phone: user.phone,
          job: user.job,
          employer: user.employer,
        };
      });
    });
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @Get('attachedUsers')
  public async findAttachedUsers() {
    return this.usersService.findAllAttached().then(async (users: IUser[]) => {
      return this.structuresService.getAllUserCompletedStructures(users);
    });
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @ApiBearerAuth('JWT')
  @Roles('admin')
  @Get('unVerifiedUsers')
  public async findUnVerifiedUsers() {
    return this.usersService.findAllUnVerified().then(async (users: IUser[]) => {
      return this.structuresService.getAllUserCompletedStructures(users);
    });
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @Delete('newsletterSubscription/:email')
  @ApiParam({ name: 'email', type: String, required: true })
  public async unsubscribeUserFromNewsletter(@Param() params): Promise<NewsletterSubscription> {
    return this.newsletterService.newsletterUnsubscribe(params.email);
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @ApiOperation({ description: 'Set user job' })
  @ApiResponse({ status: HttpStatus.OK, description: 'Return user profile' })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'User does not exist' })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Job does not exist' })
  @Put('setUserJob')
  public async setUserJob(@Body() setUserJob: SetUserJobDto): Promise<IUser> {
    this.logger.debug(`setUserJob`);
    const jobDocument = await this.jobsService.findOne(setUserJob.jobId);
    if (!jobDocument) {
      this.logger.warn(`Job does not exist: ${setUserJob.jobId}`);
      throw new HttpException('Job does not exist', HttpStatus.BAD_REQUEST);
    }

    const userDocument = await this.usersService.findById(setUserJob.userId);
    if (!userDocument) {
      this.logger.warn(`User does not exist: ${setUserJob.userId}`);
      throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
    }
    await this.usersService.updateUserJob(userDocument._id, jobDocument);
    return this.usersService.findById(setUserJob.userId);
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @ApiOperation({ description: 'Set user employer' })
  @ApiResponse({ status: HttpStatus.OK, description: 'Return user profile' })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'User does not exist' })
  @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: 'Employer does not exist' })
  @Put('setUserEmployer')
  public async setUserEmployer(@Body() setUserEmployer: SetUserEmployerDto): Promise<IUser> {
    this.logger.debug(`setUserEmployer`);
    const employerDocument = await this.employerService.findOne(setUserEmployer.employerId);
    if (!employerDocument) {
      this.logger.warn(`Employer does not exist: ${setUserEmployer.employerId}`);
      throw new HttpException('Employer does not exist', HttpStatus.BAD_REQUEST);
    }

    const userDocument = await this.usersService.findById(setUserEmployer.userId);
    if (!userDocument) {
      this.logger.warn(`User does not exist: ${setUserEmployer.userId}`);
      throw new HttpException('User does not exist', HttpStatus.BAD_REQUEST);
    }
    await this.usersService.updateUserEmployer(userDocument._id, employerDocument);
    return this.usersService.findById(setUserEmployer.userId);
  }

  @UseGuards(JwtAuthGuard, RolesGuard)
  @Roles('admin')
  @ApiBearerAuth('JWT')
  @ApiOperation({ description: 'Get deleted structures' })
  @Get('getDeletedStructures')
  public async getDeletedStructures() {
    this.logger.debug('getDeletedStructures');
    const deletedStructures = await this.structuresService.findAll(true);
    this.logger.debug(`Found ${deletedStructures.length} deleted structures`);
    return deletedStructures;
  }
}