import {
  Controller,
  Post,
  Put,
  Patch,
  Delete,
  Route,
  Security,
  Tags,
  Body,
  Path,
  Request,
  Response,
  Get,
  Query,
} from "tsoa";
import { AppDataSource } from "../database/data-source";
import HttpStatus from "../interfaces/http-status";
import HttpSuccess from "../interfaces/http-success";
import HttpStatusCode from "../interfaces/http-status";
import HttpError from "../interfaces/http-error";
import { Command } from "../entities/Command";
import { Brackets, LessThan, MoreThan, Double, In, Between, IsNull, Not, Any } from "typeorm";
import { CommandType } from "../entities/CommandType";
import { Profile, CreateProfileAllFields } from "../entities/Profile";
import { RequestWithUser, RequestWithUserWebService } from "../middlewares/user";
import { OrgRevision } from "../entities/OrgRevision";
import { ProfileEmployee } from "../entities/ProfileEmployee";
import { PosMaster } from "../entities/PosMaster";
import permission from "../interfaces/permission";
import { viewCurrentTenureOfficer } from "../entities/view/viewCurrentTenureOfficer";
import { CommandController } from "./CommandController";
import Extension from "../interfaces/extension";
import { viewRegistryOfficer } from "../entities/view/viewRegistryOfficer";
import { viewRegistryEmployee } from "../entities/view/viewRegistryEmployee";
import { Registry } from "../entities/Registry";
import { RegistryEmployee } from "../entities/RegistryEmployee";
import { TenurePositionOfficer } from "../entities/TenurePositionOfficer";
import { PosMasterAssign, PosMasterAssignDTO } from "../entities/PosMasterAssign";
import { PermissionProfile } from "../entities/PermissionProfile";
import { OrgRoot } from "../entities/OrgRoot";
import { MetaWorkflow } from "../entities/MetaWorkflow";
import { MetaState } from "../entities/MetaState";
import { MetaStateOperator } from "../entities/MetaStateOperator";
import { Workflow } from "../entities/Workflow";
import { State } from "../entities/State";
import { StateOperator } from "../entities/StateOperator";
import { StateOperatorUser } from "../entities/StateOperatorUser";
import {
  commandTypePath,
  calculateGovAge,
  calculateAge,
  calculateRetireDate,
  calculateRetireLaw,
  removeProfileInOrganize,
  setLogDataDiff,
} from "../interfaces/utils";
import CallAPI from "../interfaces/call-api";
import { PostRetireToExprofile } from "./ExRetirementController"
import { Position } from "../entities/Position";
import { PosLevel } from "../entities/PosLevel";
import { TenureLevelOfficer } from "../entities/TenureLevelOfficer";
import { TenurePositionEmployee } from "../entities/TenurePositionEmployee";
import { TenureLevelEmployee } from "../entities/TenureLevelEmployee";
import { TenurePositionExecutiveOfficer } from "../entities/TenurePositionExecutiveOfficer";

@Route("api/v1/org/DevTest")
@Tags("DevTest")
@Security("bearerAuth")
@Response(
  HttpStatusCode.INTERNAL_SERVER_ERROR,
  "เกิดข้อผิดพลาด ไม่สามารถแสดงรายการได้ กรุณาลองใหม่ในภายหลัง",
)
export class DevTestController extends Controller {
  private commandRepository = AppDataSource.getRepository(Command);
  private commandTypeRepository = AppDataSource.getRepository(CommandType);
  private orgRevisionRepo = AppDataSource.getRepository(OrgRevision);
  private orgRootRepo = AppDataSource.getRepository(OrgRoot);
  private posMasterRepo = AppDataSource.getRepository(PosMaster);
  private profileRepo = AppDataSource.getRepository(Profile);
  private profileEmpRepo = AppDataSource.getRepository(ProfileEmployee);
  private registryRepo = AppDataSource.getRepository(Registry);
  private registryEmployeeRepo = AppDataSource.getRepository(RegistryEmployee);
  private posMasterAssignRepository = AppDataSource.getRepository(PosMasterAssign);
  private permissionProfilesRepository = AppDataSource.getRepository(PermissionProfile);
  private profileEmployeeRepo = AppDataSource.getRepository(ProfileEmployee);
  private metaWorkflowRepo = AppDataSource.getRepository(MetaWorkflow);
  private metaStateRepo = AppDataSource.getRepository(MetaState);
  private metaStateOperatorRepo = AppDataSource.getRepository(MetaStateOperator);
  private workflowRepo = AppDataSource.getRepository(Workflow);
  private stateRepo = AppDataSource.getRepository(State);
  private stateOperatorRepo = AppDataSource.getRepository(StateOperator);
  private stateOperatorUserRepo = AppDataSource.getRepository(StateOperatorUser);
  private positionRepository = AppDataSource.getRepository(Position);
  private positionOfficerRepo = AppDataSource.getRepository(TenurePositionOfficer);
  private positionEmployeeRepo = AppDataSource.getRepository(TenurePositionEmployee);
  private levelOfficerRepo = AppDataSource.getRepository(TenureLevelOfficer);
  private levelEmployeeRepo = AppDataSource.getRepository(TenureLevelEmployee);
  private positionExecutiveOfficerRepo = AppDataSource.getRepository(
    TenurePositionExecutiveOfficer,
  );

  @Patch("tick-officer-registry")
  public async calculateOfficerPosition(
    @Request() req: RequestWithUser,
    @Body()
    body: {
      profileIds: string[];
    },
  ) {

    console.log("1.")
    /**
     * ===============================
     * PREPARE DATA
     * ===============================
     */
    const profile = await this.profileRepo.find({
        where: { id: In(body.profileIds) },
        relations: {
          posLevel: true,
          posType: true,
        },
      });

    if (!profile.length) return;

    const [{ today }] = await AppDataSource.query(
      "SELECT CURRENT_DATE() as today",
    );

    const orgRevision = await this.orgRevisionRepo.findOne({
      select: ["id"],
      where: {
        orgRevisionIsDraft: false,
        orgRevisionIsCurrent: true,
      },
    });

    /**
     * ===============================
     * TRANSACTION
     * ===============================
     */
    const queryRunner = AppDataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();
    console.log("2.")
    try {
      /**
       * ===============================
       * RESULT BUFFERS (SAVE ARRAY)
       * ===============================
       */
      const positionOfficerBulk: any[] = [];
      const levelOfficerBulk: any[] = [];
      const executiveOfficerBulk: any[] = [];
      console.log("3.")
      /**
       * ===============================
       * MAIN LOOP (SINGLE LOOP)
       * ===============================
       */
      for (const x of profile) {
        const currentDate =
          x.isLeave && x.leaveDate
            ? Extension.toDateOnlyString(x.leaveDate)
            : today;
        /**
         * ====================================
         * PARALLEL STORED PROCEDURES
         * ====================================
         */
        const [
          positionResult,
          levelResult,
          executiveResult,
        ] = await Promise.all([
          AppDataSource.query("CALL GetProfileSalaryPosition(?, ?)", [
            x.id,
            currentDate,
          ]),
          AppDataSource.query("CALL GetProfileSalaryLevel(?, ?)", [
            x.id,
            currentDate,
          ]),
          AppDataSource.query("CALL GetProfileSalaryExecutive(?, ?)", [
            x.id,
            currentDate,
          ]),
        ]);
        console.log("4.",x.id)
        /**
         * ====================================
         * POSITION
         * ====================================
         */
        const posRows = positionResult?.[0] ?? [];
        const posMap =
          posRows.length > 1
            ? posRows.slice(1).map((r: any, i: number) => ({
                days_diff: Number(r.days_diff) || 0,
                positionName: posRows[i]?.positionName,
              }))
            : [];

        const posCal = posMap
          .filter((p:any) => p.positionName === x.position)
          .reduce(
            (a:any, c:any) => ({
              days_diff: a.days_diff + c.days_diff,
              positionName: c.positionName,
            }),
            { days_diff: 0, positionName: null },
          );

        positionOfficerBulk.push({
          profileId: x.id,
          positionName: posCal.positionName,
          days_diff: posCal.days_diff,
          Years: Math.floor(posCal.days_diff / 365.2524),
          Months: Math.floor((posCal.days_diff / 30.4375) % 12),
          Days: Math.floor(posCal.days_diff % 30.4375),
        });
        console.log("5.",x.id)
        /**
         * ====================================
         * 2️⃣ POSITION LEVEL
         * ====================================
         */
        const lvlRows = levelResult?.[0] ?? [];
        const lvlMap =
          lvlRows.length > 1
            ? lvlRows.slice(1).map((r: any, i: number) => ({
                days_diff: Number(r.days_diff) || 0,
                positionType: lvlRows[i]?.positionType,
                positionLevel: lvlRows[i]?.positionLevel,
                positionCee: lvlRows[i]?.positionCee,
              }))
            : [];

        const lvlCal = lvlMap
          .filter(
            (l:any) =>
              l.positionLevel === x.posLevel?.posLevelName &&
              l.positionType === x.posType?.posTypeName,
          )
          .reduce(
            (a:any, c:any) => ({
              days_diff: a.days_diff + c.days_diff,
              positionType: c.positionType,
              positionLevel: c.positionLevel,
              positionCee: c.positionCee,
            }),
            {
              days_diff: 0,
              positionType: null,
              positionLevel: null,
              positionCee: null,
            },
          );

        levelOfficerBulk.push({
          profileId: x.id,
          positionType: lvlCal.positionType,
          positionLevel: lvlCal.positionLevel,
          positionCee: lvlCal.positionCee,
          days_diff: lvlCal.days_diff,
          Years: x.posLevel ? (lvlCal.days_diff / 365.2524).toFixed(4) : 0,
          Months: x.posLevel ? ((lvlCal.days_diff / 30.4375) % 12).toFixed(4) : 0,
          Days: x.posLevel ? (lvlCal.days_diff % 30.4375).toFixed(4) : 0,
        });
        console.log("6.",x.id)
        /**
         * ====================================
         * 3️⃣ POSITION EXECUTIVE
         * ====================================
         */
        const exeRows = executiveResult?.[0] ?? [];
        const exeMap =
          exeRows.length > 1
            ? exeRows.slice(1).map((r: any, i: number) => ({
                days_diff: Number(r.days_diff) || 0,
                positionExecutive: exeRows[i]?.positionExecutive,
              }))
            : [];

        const position = await this.positionRepository.findOne({
          where: {
            positionIsSelected: true,
            posMaster: {
              orgRevisionId: orgRevision?.id,
              current_holderId: x.id,
            },
          },
          order: { createdAt: "DESC" },
          relations: {
            posExecutive: true,
          },
        });

        const exeName = position?.posExecutive?.posExecutiveName;

        const exeCal = exeMap
          .filter((e:any) => exeName && e.positionExecutive === exeName)
          .reduce(
            (a:any, c:any) => ({
              days_diff: a.days_diff + c.days_diff,
              positionExecutive: c.positionExecutive,
            }),
            { days_diff: 0, positionExecutive: null },
          );

        executiveOfficerBulk.push({
          profileId: x.id,
          positionExecutiveName: exeCal.positionExecutive,
          days_diff: exeCal.days_diff,
          Years: (exeCal.days_diff / 365.2524).toFixed(4),
          Months: ((exeCal.days_diff / 30.4375) % 12).toFixed(4),
          Days: (exeCal.days_diff % 30.4375).toFixed(4),
        });
      }
      console.log("7.")
      /**
       * ===============================
       * CLEAR ALL DATA AND SAVE ARRAY (BULK)
       * ===============================
       */
      await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(this.positionOfficerRepo.target)
        .execute();

      await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(this.levelOfficerRepo.target)
        .execute();

      await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(this.positionExecutiveOfficerRepo.target)
        .execute();
      console.log("8.")
      await queryRunner.manager.save(this.positionOfficerRepo.target, positionOfficerBulk);
      await queryRunner.manager.save(this.levelOfficerRepo.target, levelOfficerBulk);
      await queryRunner.manager.save(this.positionExecutiveOfficerRepo.target,executiveOfficerBulk);
      console.log("9.")
      /**
       * ===============================
       * REGISTRY OFFICER (SYNC VIEW)
       * ===============================
       */
      const allRegis = await queryRunner.manager
        .getRepository(viewRegistryOfficer)
        .createQueryBuilder("registryOfficer")
        .where("registryOfficer.profileId IN (:...profileIds)", {
          profileIds: new Set(profile.map((p) => p.id))
        })
        .getMany();

      const mapRegistryData = allRegis.map((x) => ({
        ...x,
        isProbation: Boolean(x.isProbation),
        isLeave: Boolean(x.isLeave),
        isRetirement: Boolean(x.isRetirement),
        Educations: x.Educations ? JSON.stringify(x.Educations) : "",
      }));
      console.log("10.")

      await queryRunner.manager
        .createQueryBuilder()
        .delete()
        .from(this.registryRepo.target)
        .execute();

      if (mapRegistryData.length > 0) {
        await queryRunner.manager.save(this.registryRepo.target, mapRegistryData);
      }
      console.log("11.")
      /**
       * ===============================
       * COMMIT
       * ===============================
       */
      await queryRunner.commitTransaction();
    } catch (error) {
      await queryRunner.rollbackTransaction();
      throw error;
    } finally {
      await queryRunner.release();
    }
  }

  @Post("getDNA")
  public async GetData(
    @Request() req: RequestWithUser
  ){
    let _data: any = {
      root: null,
      child1: null,
      child2: null,
      child3: null,
      child4: null,
    };
    
    _data = await new permission().PermissionOrgList(req, "COMMAND");
    return new HttpSuccess(_data);
  }

  @Post("calculateGovAge")
  public async calculateGovAge(
    @Request() req: RequestWithUser,
    @Body()
    body: {
      profileId: string;
    },
  ){
    return new HttpSuccess(await calculateGovAge(body.profileId, "OFFICER"));
  }

  /**
   * @summary Test Job กวาดออกคำสั่ง ทำงานทุกๆตี2
   */
  @Post("cronjobCommand")
  async CronjobCommand() {
    const commandController = new CommandController();
    await commandController.cronjobCommand();
  }

  /**
   * @summary payload & Endpoint ออกคำสั่ง
   */
  @Put("path-excec/{id}")
  async Bright(
    @Path() id: string,
    @Request() request: RequestWithUser,
  ) {
    const command = await this.commandRepository.findOne({
      where: { id: id },
      relations: ["commandType", "commandRecives", "commandSends", "commandSends.commandSendCCs"],
    });
    if (!command) {
      throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
    }
    const path = commandTypePath(command.commandType.code);
    return new HttpSuccess({
      path: path + "/excecute",
      refIds: command.commandRecives
        .filter((x) => x.refId != null)
        .map((x) => ({
          refId: x.refId,
          commandNo: command.commandNo,
          commandYear: command.commandYear,
          commandId: command.id,
          remark: command.positionDetail,
          amount: x.amount,
          amountSpecial: x.amountSpecial,
          positionSalaryAmount: x.positionSalaryAmount,
          mouthSalaryAmount: x.mouthSalaryAmount,
          commandCode: command.commandType.commandCode,
          commandName: command.commandType.name,
          commandDateAffect: command.commandExcecuteDate,
          commandDateSign: command.commandAffectDate,
        })),
    });
  }

   /**
   * API รายละเอียดรายการคำสั่ง tab4 แนบท้าย
   * @summary API รายละเอียดรายการคำสั่ง tab4 แนบท้าย
   * @param {string} id  Id  คำสั่ง
   * @param {string} profileId  profileId 
   */
  @Get("tab4/attachment/{id}/{profileId}")
  async GetByIdTab4Attachment(
    @Path() id: string, 
    @Path() profileId: string, 
    @Request() request: RequestWithUser
  ) {
    await new permission().PermissionGet(request, "COMMAND");

    let profile: Profile | ProfileEmployee | null = null;
    profile =  await this.profileRepo.findOne({ where: { id: profileId } });
    if (!profile) {
      profile =  await this.profileEmpRepo.findOne({ where: { id: profileId } });
      if (!profile)
        throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลบุคคลากรนี้");
    }

    const command = await this.commandRepository.findOne({
      where: { id },
      relations: ["commandType", "commandRecives"],
    });
    if (!command) {
      throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบข้อมูลคำสั่งนี้");
    }

    let _command: any = [];
    const path = commandTypePath(command.commandType.code);
    if (path == null) throw new HttpError(HttpStatusCode.NOT_FOUND, "ไม่พบประเภทคำสั่งนี้ในระบบ");
    await new CallAPI()
      .PostData(request, path + "/attachment", {
        refIds: command.commandRecives
          .filter((x) => 
              x.refId != null &&
              x.profileId != null && x.profileId ==  profileId
          )
          .map((x) => ({
            refId: x.refId,
            Sequence: x.order,
            CitizenId: x.citizenId,
            Prefix: x.prefix,
            FirstName: x.firstName,
            LastName: x.lastName,
            Amount: x.amount,
            PositionSalaryAmount: x.positionSalaryAmount,
            MouthSalaryAmount: x.mouthSalaryAmount,
            RemarkHorizontal: x.remarkHorizontal,
            RemarkVertical: x.remarkVertical,
            CommandYear: command.commandYear,
            CommandExcecuteDate: command.commandExcecuteDate,
          })),
      })
      .then(async (res) => {
        _command = res;
      })
      .catch(() => {});

    let issue =
      command.isBangkok == "OFFICE"
        ? "สำนักปลัดกรุงเทพมหานคร"
        : command.isBangkok == "BANGKOK"
          ? "กรุงเทพมหานคร"
          : null;
    if (issue == null) {
      const orgRevisionActive = await this.orgRevisionRepo.findOne({
        where: { orgRevisionIsCurrent: true, orgRevisionIsDraft: false },
        relations: ["posMasters", "posMasters.orgRoot"],
      });
      if (orgRevisionActive != null) {
        const profile = await this.profileRepo.findOne({
          where: {
            keycloak: command.createdUserId.toString(),
          },
        });
        if (profile != null) {
          issue =
            orgRevisionActive?.posMasters?.filter((x) => x.current_holderId == profile.id)[0]
              ?.orgRoot?.orgRootName || null;
        }
      }
    }
    if (issue == null) issue = "...................................";
    return new HttpSuccess({
      template: command.commandType.fileAttachment,
      reportName: "xlsx-report",
      data: {
        data: _command,
        issuerOrganizationName: issue,
        commandNo: command.commandNo == null ? "" : Extension.ToThaiNumber(command.commandNo),
        commandYear:
          command.commandYear == null
            ? ""
            : Extension.ToThaiNumber(Extension.ToThaiYear(command.commandYear).toString()),
        commandExcecuteDate:
          command.commandExcecuteDate == null
            ? ""
            : Extension.ToThaiNumber(Extension.ToThaiFullDate2(command.commandExcecuteDate)),
      },
    });
  }

}
