import { EntityManager, EntityTarget, In } from "typeorm";
import { AppDataSource } from "../database/data-source";
import { OrgRevision } from "../entities/OrgRevision";
import { PosMaster } from "../entities/PosMaster";
import { Position } from "../entities/Position";
import { OrgRoot } from "../entities/OrgRoot";
import { OrgChild1 } from "../entities/OrgChild1";
import { OrgChild2 } from "../entities/OrgChild2";
import { OrgChild3 } from "../entities/OrgChild3";
import { OrgChild4 } from "../entities/OrgChild4";
import { PosMasterAct } from "../entities/PosMasterAct";
import { PosMasterAssign } from "../entities/PosMasterAssign";
import { PermissionOrg } from "../entities/PermissionOrg";
import { PermissionProfile } from "../entities/PermissionProfile";
import { EmployeePosMaster } from "../entities/EmployeePosMaster";
import { EmployeeTempPosMaster } from "../entities/EmployeeTempPosMaster";
import { EmployeePosition } from "../entities/EmployeePosition";
import { orgStructureCache } from "../utils/OrgStructureCache";

export interface ClearOldOrgRevisionSummary {
  orgRevisionId: string;
  orgRevisionName: string;
  deleted: {
    positions: number;
    employeePositionsByPosMaster: number;
    employeePositionsByTempPosMaster: number;
    posMasterActsByParent: number;
    posMasterActsByChild: number;
    posMasterAssigns: number;
    posMasters: number;
    employeePosMasters: number;
    employeeTempPosMasters: number;
    permissionOrgs: number;
    permissionProfiles: number;
    orgChild4s: number;
    orgChild3s: number;
    orgChild2s: number;
    orgChild1s: number;
    orgRoots: number;
    orgRevisions: number;
  };
}

interface OrgRevisionSnapshot {
  id: string;
  orgRevisionName: string;
  orgRevisionIsCurrent: boolean;
  orgRevisionIsDraft: boolean;
}

export async function clearOldOrgRevisionData(
  orgRevisionId: string,
): Promise<ClearOldOrgRevisionSummary> {
  const result = await AppDataSource.transaction(async (manager) => {
    const orgRevision = await manager.findOne(OrgRevision, {
      where: { id: orgRevisionId },
      select: ["id", "orgRevisionName", "orgRevisionIsCurrent", "orgRevisionIsDraft"],
    });

    if (!orgRevision) {
      throw new Error(`ไม่พบ orgRevision ที่ต้องการล้างข้อมูล: ${orgRevisionId}`);
    }

    validateOrgRevisionForDeletion(orgRevision);

    const [posMasters, orgRoots, employeePosMasters, employeeTempPosMasters] = await Promise.all([
      manager.find(PosMaster, {
        where: { orgRevisionId },
        select: ["id"],
      }),
      manager.find(OrgRoot, {
        where: { orgRevisionId },
        select: ["id"],
      }),
      manager.find(EmployeePosMaster, {
        where: { orgRevisionId },
        select: ["id"],
      }),
      manager.find(EmployeeTempPosMaster, {
        where: { orgRevisionId },
        select: ["id"],
      }),
    ]);

    const posMasterIds = posMasters.map((item) => item.id);
    const orgRootIds = orgRoots.map((item) => item.id);
    const employeePosMasterIds = employeePosMasters.map((item) => item.id);
    const employeeTempPosMasterIds = employeeTempPosMasters.map((item) => item.id);

    const [
      positionsCount,
      employeePositionsByPosMasterCount,
      employeePositionsByTempPosMasterCount,
      posMasterActsByParentCount,
      posMasterActsByChildCount,
      posMasterAssignsCount,
      permissionOrgsCount,
      permissionProfilesCount,
      orgChild4sCount,
      orgChild3sCount,
      orgChild2sCount,
      orgChild1sCount,
    ] = await Promise.all([
      countByIds(manager, Position, "posMasterId", posMasterIds),
      countByIds(manager, EmployeePosition, "posMasterId", employeePosMasterIds),
      countByIds(manager, EmployeePosition, "posMasterTempId", employeeTempPosMasterIds),
      countByIds(manager, PosMasterAct, "posMasterId", posMasterIds),
      countByIds(manager, PosMasterAct, "posMasterChildId", posMasterIds),
      countByIds(manager, PosMasterAssign, "posMasterId", posMasterIds),
      countByIds(manager, PermissionOrg, "orgRootId", orgRootIds),
      countByIds(manager, PermissionProfile, "orgRootId", orgRootIds),
      manager.count(OrgChild4, { where: { orgRevisionId } }),
      manager.count(OrgChild3, { where: { orgRevisionId } }),
      manager.count(OrgChild2, { where: { orgRevisionId } }),
      manager.count(OrgChild1, { where: { orgRevisionId } }),
    ]);

    if (positionsCount > 0) {
      await manager.delete(Position, { posMasterId: In(posMasterIds) });
    }
    if (employeePositionsByPosMasterCount > 0) {
      await manager.delete(EmployeePosition, { posMasterId: In(employeePosMasterIds) });
    }
    if (employeePositionsByTempPosMasterCount > 0) {
      await manager.delete(EmployeePosition, { posMasterTempId: In(employeeTempPosMasterIds) });
    }
    if (posMasterActsByParentCount > 0) {
      await manager.delete(PosMasterAct, { posMasterId: In(posMasterIds) });
    }
    if (posMasterActsByChildCount > 0) {
      await manager.delete(PosMasterAct, { posMasterChildId: In(posMasterIds) });
    }
    if (posMasterAssignsCount > 0) {
      await manager.delete(PosMasterAssign, { posMasterId: In(posMasterIds) });
    }

    const posMastersCount = posMasterIds.length;
    const employeePosMastersCount = employeePosMasterIds.length;
    const employeeTempPosMastersCount = employeeTempPosMasterIds.length;

    if (posMastersCount > 0) {
      await manager.delete(PosMaster, { orgRevisionId });
    }
    if (employeePosMastersCount > 0) {
      await manager.delete(EmployeePosMaster, { orgRevisionId });
    }
    if (employeeTempPosMastersCount > 0) {
      await manager.delete(EmployeeTempPosMaster, { orgRevisionId });
    }

    if (permissionOrgsCount > 0) {
      await manager.delete(PermissionOrg, { orgRootId: In(orgRootIds) });
    }
    if (permissionProfilesCount > 0) {
      await manager.delete(PermissionProfile, { orgRootId: In(orgRootIds) });
    }

    if (orgChild4sCount > 0) {
      await manager.delete(OrgChild4, { orgRevisionId });
    }
    if (orgChild3sCount > 0) {
      await manager.delete(OrgChild3, { orgRevisionId });
    }
    if (orgChild2sCount > 0) {
      await manager.delete(OrgChild2, { orgRevisionId });
    }
    if (orgChild1sCount > 0) {
      await manager.delete(OrgChild1, { orgRevisionId });
    }

    const orgRootsCount = orgRootIds.length;
    if (orgRootsCount > 0) {
      await manager.delete(OrgRoot, { orgRevisionId });
    }

    await manager.delete(OrgRevision, { id: orgRevisionId });

    return {
      orgRevisionId: orgRevision.id,
      orgRevisionName: orgRevision.orgRevisionName,
      deleted: {
        positions: positionsCount,
        employeePositionsByPosMaster: employeePositionsByPosMasterCount,
        employeePositionsByTempPosMaster: employeePositionsByTempPosMasterCount,
        posMasterActsByParent: posMasterActsByParentCount,
        posMasterActsByChild: posMasterActsByChildCount,
        posMasterAssigns: posMasterAssignsCount,
        posMasters: posMastersCount,
        employeePosMasters: employeePosMastersCount,
        employeeTempPosMasters: employeeTempPosMastersCount,
        permissionOrgs: permissionOrgsCount,
        permissionProfiles: permissionProfilesCount,
        orgChild4s: orgChild4sCount,
        orgChild3s: orgChild3sCount,
        orgChild2s: orgChild2sCount,
        orgChild1s: orgChild1sCount,
        orgRoots: orgRootsCount,
        orgRevisions: 1,
      },
    };
  });

  orgStructureCache.invalidate(orgRevisionId);
  return result;
}

function validateOrgRevisionForDeletion(orgRevision: OrgRevisionSnapshot): void {
  if (orgRevision.orgRevisionIsCurrent) {
    throw new Error(`ไม่สามารถลบ orgRevision ปัจจุบันได้: ${orgRevision.id}`);
  }

  if (orgRevision.orgRevisionIsDraft) {
    throw new Error(`ไม่สามารถลบ orgRevision แบบร่างได้ด้วยสคริปต์นี้: ${orgRevision.id}`);
  }
}

async function countByIds<Entity extends object>(
  manager: EntityManager,
  entity: EntityTarget<Entity>,
  field: keyof Entity,
  ids: string[],
): Promise<number> {
  if (ids.length === 0) {
    return 0;
  }

  const alias = "entity";
  return manager
    .createQueryBuilder(entity, alias)
    .where(`${alias}.${String(field)} IN (:...ids)`, { ids })
    .getCount();
}
