import {
  Body,
  Controller,
  Delete,
  Get,
  Patch,
  Path,
  Post,
  Query,
  Request,
  Route,
  Security,
  Tags,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpSuccess from "../interfaces/http-success";
import HttpStatus from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { RequestWithUser } from "../middlewares/user";
import { Profile } from "../entities/Profile";
import { CreateDevelopmentRequest, DevelopmentRequest } from "../entities/DevelopmentRequest";
import permission from "../interfaces/permission";
import { Brackets } from "typeorm";
import { DevelopmentProject } from "../entities/DevelopmentProject";
import { ProfileDevelopment } from "../entities/ProfileDevelopment";
import { ProfileDevelopmentHistory } from "../entities/ProfileDevelopmentHistory";
import { setLogDataDiff } from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
import { OrgRevision } from "../entities/OrgRevision";
import { OrgRoot } from "../entities/OrgRoot";
@Route("api/v1/org/profile/development-request")
@Tags("DevelopmentRequest")
@Security("bearerAuth")
export class DevelopmentRequestController extends Controller {
  private profileRepository = AppDataSource.getRepository(Profile);
  private profileDevelopmentRepository = AppDataSource.getRepository(ProfileDevelopment);
  private developmentRequestRepository = AppDataSource.getRepository(DevelopmentRequest);
  private developmentProjectRepository = AppDataSource.getRepository(DevelopmentProject);
  private developmentHistoryRepository = AppDataSource.getRepository(ProfileDevelopmentHistory);
  private orgRevisionRepository = AppDataSource.getRepository(OrgRevision);
  private orgRootRepo = AppDataSource.getRepository(OrgRoot);

  @Get("user")
  public async getDevelopmentRequestUser(
    @Request() req: RequestWithUser,
    @Query("keyword") keyword: string = "",
    @Query("page") page: number = 1,
    @Query("pageSize") pageSize: number = 10,
    @Query("status") status?: string,
    @Query("sortBy") sortBy?: string,
    @Query("descending") descending?: boolean,
  ) {
    const profile = await this.profileRepository.findOneBy({ keycloak: req.user.sub });
    if (!profile) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
    }

    let query = await AppDataSource.getRepository(DevelopmentRequest)
      .createQueryBuilder("developmentRequest")
      .andWhere(
        status == undefined || status.trim().toUpperCase() == "ALL" || status == ""
          ? "1=1"
          : "developmentRequest.status = :status",
        {
          status: status == undefined || status == null ? "" : status.trim().toUpperCase(),
        },
      )
      .andWhere("developmentRequest.profileId = :profileId", { profileId: profile.id })
      .andWhere(
        new Brackets((qb) => {
          qb.where(
            keyword != null && keyword != "" ? "developmentRequest.reason LIKE :keyword" : "1=1",
            {
              keyword: `%${keyword}%`,
            },
          )
            .orWhere(
              keyword != null && keyword != "" ? "developmentRequest.name LIKE :keyword" : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentTarget LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentResults LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentReport LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            );
        }),
      )
      .orderBy("developmentRequest.createdAt", "DESC");

    if (sortBy) {
      query = query.orderBy(`developmentRequest.${sortBy}`, descending ? "DESC" : "ASC");
    }

    const [lists, total] = await query
      .skip((page - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount();
    return new HttpSuccess({ data: lists, total });
  }

  @Get("admin")
  public async getDevelopmentRequestAdmin(
    @Request() request: RequestWithUser,
    @Query("status") status: string,
    @Query("keyword") keyword: string = "",
    @Query("page") page: number = 1,
    @Query("pageSize") pageSize: number = 10,
    @Query("sortBy") sortBy?: string,
    @Query("descending") descending?: boolean,
  ) {
    let data = await new permission().PermissionOrgList(request, "SYS_REGISTRY_OFFICER");
    const orgRevisionPublish = await this.orgRevisionRepository
      .createQueryBuilder("orgRevision")
      .where("orgRevision.orgRevisionIsDraft = false")
      .andWhere("orgRevision.orgRevisionIsCurrent = true")
      .getOne();
    let query = await AppDataSource.getRepository(DevelopmentRequest)
      .createQueryBuilder("developmentRequest")
      .leftJoinAndSelect("developmentRequest.profile", "profile")
      .leftJoinAndSelect("profile.current_holders", "current_holders")
      .leftJoinAndSelect("current_holders.orgRevision", "orgRevision")
      .andWhere(
        status == undefined || status.trim().toUpperCase() == "ALL" || status == ""
          ? "1=1"
          : "developmentRequest.status = :status",
        {
          status: status == undefined || status == null ? "" : status.trim().toUpperCase(),
        },
      )
      .andWhere(orgRevisionPublish ? `current_holders.orgRevisionId = :revisionId` : "1=1", {
        revisionId: orgRevisionPublish?.id,
      })
      .andWhere(
        data.root != undefined && data.root != null
          ? data.root[0] != null
            ? `current_holders.orgRootId IN (:...root)`
            : `current_holders.orgRootId is null`
          : "1=1",
        {
          root: data.root,
        },
      )
      .andWhere(
        data.child1 != undefined && data.child1 != null
          ? data.child1[0] != null
            ? `current_holders.orgChild1Id IN (:...child1)`
            // : `current_holders.orgChild1Id is ${data.privilege == "PARENT" ? "not null" : "null"}`
            : `current_holders.orgChild1Id is null`
          : "1=1",
        {
          child1: data.child1,
        },
      )
      .andWhere(
        data.child2 != undefined && data.child2 != null
          ? data.child2[0] != null
            ? `current_holders.orgChild2Id IN (:...child2)`
            : `current_holders.orgChild2Id is null`
          : "1=1",
        {
          child2: data.child2,
        },
      )
      .andWhere(
        data.child3 != undefined && data.child3 != null
          ? data.child3[0] != null
            ? `current_holders.orgChild3Id IN (:...child3)`
            : `current_holders.orgChild3Id is null`
          : "1=1",
        {
          child3: data.child3,
        },
      )
      .andWhere(
        data.child4 != undefined && data.child4 != null
          ? data.child4[0] != null
            ? `current_holders.orgChild4Id IN (:...child4)`
            : `current_holders.orgChild4Id is null`
          : "1=1",
        {
          child4: data.child4,
        },
      )
      .andWhere(
        new Brackets((qb) => {
          qb.where(
            keyword != null && keyword != "" ? "developmentRequest.reason LIKE :keyword" : "1=1",
            {
              keyword: `%${keyword}%`,
            },
          )
            .orWhere(
              keyword != null && keyword != "" ? "developmentRequest.name LIKE :keyword" : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentTarget LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentResults LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.developmentReport LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            )
            .orWhere(
              keyword != null && keyword != ""
                ? "developmentRequest.createdFullName LIKE :keyword"
                : "1=1",
              {
                keyword: `%${keyword}%`,
              },
            );
        }),
      )
      .orderBy("developmentRequest.createdAt", "DESC");

    if (sortBy) {
      query = query.orderBy(`developmentRequest.${sortBy}`, descending ? "DESC" : "ASC");
    }

    const [lists, total] = await query
      .skip((page - 1) * pageSize)
      .take(pageSize)
      .getManyAndCount();

    const _data = lists.map((item) => ({ ...item, profile: null }));
    return new HttpSuccess({ data: _data, total });
  }

  @Get("admin/{id}")
  public async getDevelopmentRequestByUser(@Path() id: string, @Request() req: RequestWithUser) {
    let _workflow = await new permission().Workflow(req, id, "SYS_REGISTRY_OFFICER");
    if (_workflow == false) await new permission().PermissionGet(req, "SYS_REGISTRY_OFFICER");
    const data = await this.developmentRequestRepository.findOne({
      where: { id: id },
      relations: ["developmentProjects"],
    });
    if (!data) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
    const _data = {
      ...data,
      developmentProjects: data.developmentProjects.map((x) => x.name),
    };
    return new HttpSuccess(_data);
  }

  @Get("user/{id}")
  public async getDevelopmentRequestByAdmin(@Path() id: string, @Request() req: RequestWithUser) {
    const data = await this.developmentRequestRepository.findOne({
      where: { id: id },
      relations: ["developmentProjects"],
    });
    if (!data) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
    const _data = {
      ...data,
      developmentProjects: data.developmentProjects.map((x) => x.name),
    };
    return new HttpSuccess(_data);
  }

  @Post()
  public async newDevelopmentRequest(
    @Request() req: RequestWithUser,
    @Body() body: CreateDevelopmentRequest,
  ) {
    const profile = await this.profileRepository.findOne({
      relations: {
        posLevel: true,
        posType: true,
        current_holders: true
      },
      where: { 
        keycloak: req.user.sub,
        current_holders: {
          orgRevision: {
            orgRevisionIsCurrent: true,
            orgRevisionIsDraft: false
          }
        }
      }
    });
    if (!profile) {
      throw new HttpError(HttpStatus.BAD_REQUEST, "ไม่พบ profile ดังกล่าว");
    }
    const orgRoot = await this.orgRootRepo.findOne({
      select: {
        id: true,
        isDeputy: true
      },
      where: {
        id: profile.current_holders.find(x => x.orgRootId)!.orgRootId ?? ""
      }
    })
    const before = null;
    const data = new DevelopmentRequest();

    const meta = {
      createdUserId: req.user.sub,
      createdFullName: req.user.name,
      lastUpdateUserId: req.user.sub,
      lastUpdateFullName: req.user.name,
      createdAt: new Date(),
      lastUpdatedAt: new Date(),
    };

    Object.assign(data, { ...body, ...meta });
    data.profileId = profile.id;
    data.status = "PENDING";
    await this.developmentRequestRepository.save(data, { data: req });
    setLogDataDiff(req, { before, after: data });

    if (body.developmentProjects != null) {
      await Promise.all(
        body.developmentProjects.map(async (x) => {
          let developmentProject = new DevelopmentProject();
          developmentProject.name = x;
          developmentProject.createdUserId = req.user.sub;
          developmentProject.createdFullName = req.user.name;
          developmentProject.lastUpdateUserId = req.user.sub;
          developmentProject.lastUpdateFullName = req.user.name;
          developmentProject.createdAt = new Date();
          developmentProject.lastUpdatedAt = new Date();
          developmentProject.developmentRequestId = data.id;
          await this.developmentProjectRepository.save(developmentProject, { data: req });
          setLogDataDiff(req, { before, after: developmentProject });
        }),
      );
    }
    await new CallAPI()
      .PostData(req, "/org/workflow/add-workflow", {
        refId: data.id,
        sysName: "REGISTRY_IDP",
        posLevelName: profile.posLevel.posLevelName,
        posTypeName: profile.posType.posTypeName,
        fullName: `${profile.prefix}${profile.firstName} ${profile.lastName}`,
        isDeputy: orgRoot?.isDeputy ?? false,
        orgRootId: orgRoot?.id ?? null
      })
      .catch((error) => {
        console.error("Error calling API:", error);
      });
    return new HttpSuccess(data.id);
  }

  @Patch("user/{developmentId}")
  public async editUserDevelopmentRequest(
    @Request() req: RequestWithUser,
    @Body() body: CreateDevelopmentRequest,
    @Path() developmentId: string,
  ) {
    const record = await this.developmentRequestRepository.findOne({
      where: { id: developmentId },
      relations: ["developmentProjects"],
    });
    const before = structuredClone(record);
    if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
    Object.assign(record, body);
    record.lastUpdateUserId = req.user.sub;
    record.lastUpdateFullName = req.user.name;
    record.lastUpdatedAt = new Date();

    await this.developmentRequestRepository.save(record, { data: req });
    setLogDataDiff(req, { before, after: record });
    await this.developmentProjectRepository.delete({ developmentRequestId: record.id });
    if (body.developmentProjects != null) {
      await Promise.all(
        body.developmentProjects.map(async (x) => {
          let developmentProject = new DevelopmentProject();
          developmentProject.name = x;
          developmentProject.createdUserId = req.user.sub;
          developmentProject.createdFullName = req.user.name;
          developmentProject.lastUpdateUserId = req.user.sub;
          developmentProject.lastUpdateFullName = req.user.name;
          developmentProject.createdAt = new Date();
          developmentProject.lastUpdatedAt = new Date();
          developmentProject.developmentRequestId = record.id;
          await this.developmentProjectRepository.save(developmentProject, { data: req });
          setLogDataDiff(req, { before: null, after: record });
        }),
      );
    }
    return new HttpSuccess(record.id);
  }

  @Patch("admin/{developmentId}")
  public async editAdminDevelopmentRequest(
    @Request() req: RequestWithUser,
    @Body()
    requestBody: { reason?: string | null; status: string },
    @Path() developmentId: string,
  ) {
    const record = await this.developmentRequestRepository.findOne({
      where: { id: developmentId },
      relations: ["developmentProjects"],
    });
    if (!record) throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูล");
    await new permission().PermissionUpdate(req, "SYS_REGISTRY_OFFICER");

    if (requestBody.status == "APPROVE" && record.status == "PENDING") {
      let profileDevelopment = new ProfileDevelopment();
      const meta = {
        createdUserId: req.user.sub,
        createdFullName: req.user.name,
        lastUpdateUserId: req.user.sub,
        lastUpdateFullName: req.user.name,
        createdAt: new Date(),
        lastUpdatedAt: new Date(),
      };
      Object.assign(profileDevelopment, {
        ...record,
        ...meta,
        id: undefined,
        type: "REQUEST",
        kpiDevelopmentId: record.id,
        developmentProjects: [],
      });
      await this.profileDevelopmentRepository.save(profileDevelopment, { data: req });

      const history = new ProfileDevelopmentHistory();
      Object.assign(history, {
        ...profileDevelopment,
        id: undefined,
        type: "REQUEST",
        kpiDevelopmentId: record.id,
        developmentProjects: [],
      });
      history.profileDevelopmentId = profileDevelopment.id;
      await this.developmentHistoryRepository.save(history, { data: req });

      if (record.developmentProjects != null) {
        await Promise.all(
          record.developmentProjects.map(async (x) => {
            let developmentProject = new DevelopmentProject();
            let developmentProjectHistory = new DevelopmentProject();
            Object.assign(developmentProject, {
              ...meta,
              id: undefined,
              name: record.name,
              profileDevelopmentId: profileDevelopment.id,
            });
            Object.assign(developmentProject, {
              ...meta,
              id: undefined,
              name: record.name,
              profileDevelopmentHistoryId: history.id,
            });
            await Promise.all([
              this.developmentProjectRepository.save(developmentProject, { data: req }),
              setLogDataDiff(req, { before: null, after: developmentProject }),
              this.developmentProjectRepository.save(developmentProjectHistory, { data: req }),
            ]);
          }),
        );
      }
    }
    const before = structuredClone(record);
    requestBody.status = requestBody.status.trim().toUpperCase();
    Object.assign(record, requestBody);

    record.lastUpdateUserId = req.user.sub;
    record.lastUpdateFullName = req.user.name;
    record.lastUpdatedAt = new Date();

    await this.developmentRequestRepository.save(record, { data: req });
    setLogDataDiff(req, { before, after: record });
    return new HttpSuccess(record.id);
  }

  @Delete("{id}")
  public async deleteDevelopmentRequest(@Path() id: string, @Request() req: RequestWithUser) {
    const developmentRequest = await this.developmentRequestRepository.findOne({
      where: { id },
    });
    if (!developmentRequest) {
      throw new HttpError(HttpStatus.NOT_FOUND, "ไม่พบข้อมูลคำขอนี้");
    }
    await this.developmentProjectRepository.delete({ developmentRequestId: id });
    await this.developmentRequestRepository.delete({ id });

    return new HttpSuccess();
  }
}
