const jwt = require("jsonwebtoken");
const bcrypt = require("bcryptjs");
const dbSAPPS = require("../../helpers/db");
require("dotenv").config();
const config = require("../../../../config.json");
const { Op } = require("sequelize");

// Cache for territory versions (heavy query, rarely changes)
const territoryVersionCache = new Map();
const VERSION_CACHE_TTL = 10 * 60 * 1000; // 10 minutes

module.exports = {
  getTerritoryHierarchy,
};

// Contoh penggunaan:
// const user = {
//     employee: {
//         id: 1,
//         company_id: 1,
//         territory_id: 10
//     }
// };

/**
 * Fungsi utama untuk mendapatkan hierarki wilayah
 * @param {Object} user - Data user yang berisi informasi employee
 * @returns {Object} - Objek yang berisi semua data hierarki
 */
async function getTerritoryHierarchy(user) {
  const hierarchyData = {
    employeeTerritory: [],
    territoryVersion: [],
    territoryVersionChilds: [],
    territoryParent: [],
    territoryChild: [],
    territories: [],
    hierarchyTerritories: [],
    employeeTerritoryParent: [],
    bankEmployeeTerritoryParent: [],
    workflowTerritories: [],
    employeeTerritoryChild: [],
    subCoordinateTerritories: [],
    subCoordinateEmployeeTerritories: [],
    territoriesIds: [],
    employeeBank: [],
    territoryItemGroup: [],
    itemGroupIds: [],
    workflowTerritoryDirect: [],
  };

  try {
    // Step 1: Dapatkan territory employee
    await getEmployeeTerritory(user, hierarchyData);

    // Step 2: Dapatkan semua versi territory yang valid untuk perusahaan employee
    await getTerritoryVersions(user, hierarchyData);

    // Step 3: Bangun hierarki dan data terkait
    buildHierarchyData(hierarchyData);

    // Step 4: Dapatkan data item group untuk territory
    await getTerritoryItemGroup(hierarchyData);

    // Step 5: Dapatkan data employee parent
    await getEmployeeTerritoryParents(hierarchyData);

    // Step 6: Dapatkan data employee child
    await getEmployeeTerritoryChildren(hierarchyData);

    // Step 7: Dapatkan data rekening bank employee
    await getEmployeeBank(user, hierarchyData);

    // Step 8: Dapatkan data employee parent direct
    await getDirectParent(hierarchyData);

    return {
      territories: hierarchyData.territories,
      hierarchyTerritories: hierarchyData.hierarchyTerritories,
      workflowTerritories: hierarchyData.workflowTerritories,
      subCoordinateTerritories: hierarchyData.subCoordinateTerritories,
      subCoordinateEmployeeTerritories: hierarchyData.subCoordinateEmployeeTerritories,
      employeeTerritoryParent: hierarchyData.employeeTerritoryParent,
      employeeTerritoryChild: hierarchyData.employeeTerritoryChild,
      employeeBank: hierarchyData.employeeBank,
      territoryItemGroup: hierarchyData.territoryItemGroup,
      itemGroupIds: hierarchyData.itemGroupIds,
      workflowTerritoryDirect: hierarchyData.workflowTerritoryDirect,
      bankEmployeeTerritoryParent: hierarchyData.bankEmployeeTerritoryParent,
      success: true,
      message: "Loaded successfully!",
    };
  } catch (error) {
    const errorMessage = error?.message || "Failed to load, please try again!";
    return {
      success: false,
      message: errorMessage,
    };
  }
}

/**
 * Mendapatkan territory dari employee yang bersangkutan
 */
async function getEmployeeTerritory(user, hierarchyData) {
  if (user?.employee?.id) {
    hierarchyData.employeeTerritory = await dbSAPPS.EmployeeTerritory.findAll({
      where: { employee_id: user.employee.id },
      include: [
        {
          model: dbSAPPS.Territory,
          as: "territory",
          attributes: ["id", "name", "display_name"],
        },
      ],
      attributes: ["employee_id", "territory_id"],
    });
  }
}

/**
 * Mendapatkan semua versi territory yang valid untuk perusahaan employee
 */
async function getTerritoryVersions(user, hierarchyData) {
  if (user?.employee?.id && user?.employee?.company_id && hierarchyData.employeeTerritory?.length > 0) {
    const companyId = user.employee.company_id;
    const now = Date.now();
    
    // Check cache
    const cached = territoryVersionCache.get(companyId);
    let getTerritoryVersion;

    if (cached && (now - cached.timestamp < VERSION_CACHE_TTL)) {
        getTerritoryVersion = cached.data;
    } else {
        const today = new Date();
        getTerritoryVersion = await dbSAPPS.TerritoryVersion.findAll({
        attributes: ["id", "display_name", "valid_from", "valid_to", "enabled"],
        where: {
            [Op.and]: [
            { company_id: companyId },
            { enabled: true },
            { valid_from: { [Op.lte]: today } },
            { [Op.or]: [{ valid_to: { [Op.gte]: today } }, { valid_to: null }] },
            ],
        },
        include: [
            {
            model: dbSAPPS.TerritoryVersionChild,
            as: "territoryversionchilds",
            attributes: ["id", "name", "parent_id", "territory_id", "display_name"],
            },
        ],
        });
        
        // Update cache
        if (getTerritoryVersion) {
            territoryVersionCache.set(companyId, {
                timestamp: now,
                data: getTerritoryVersion
            });
        }
    }

    if (getTerritoryVersion?.length > 0) {
      hierarchyData.territoryVersion = getTerritoryVersion;
      // Use map to create new array references to avoid mutating cached objects if downstream code modifies them
      // Although purely reading here, flatMap creates a new array.
      hierarchyData.territoryVersionChilds = getTerritoryVersion.flatMap((version) => version.territoryversionchilds);
    }
  }
}

/**
 * Membangun struktur data hierarki
 */
function buildHierarchyData(hierarchyData) {
  const buildHierarchy = (parent_id = null) => {
    const children = hierarchyData.territoryVersionChilds.filter((c) => c.parent_id === parent_id && c.territory_id);
    return children.map((child) => ({
      id: child.territory_id,
      name: child.display_name,
      display_name: child.display_name,
      children: buildHierarchy(child.territory_id),
    }));
  };

  hierarchyData.hierarchyTerritories = buildHierarchy();

  if (hierarchyData.employeeTerritory.length > 0) {
    const empTerrId = hierarchyData.employeeTerritory[0].territory.id;
    hierarchyData.territoryChild = buildHierarchy(empTerrId);
    hierarchyData.territories = [
      {
        id: empTerrId,
        territory_id: empTerrId,
        name: hierarchyData.employeeTerritory[0].territory.name,
        children: hierarchyData.territoryChild,
      },
    ];
    hierarchyData.territoryParent = getParentHierarchy(empTerrId, hierarchyData.territoryVersionChilds);
  }
}

/**
 * Mendapatkan parent hierarchy
 */
function getParentHierarchy(territory_id, territoryVersionChilds) {
  const hierarchy = [];
  const findParent = (id) => {
    const parent = territoryVersionChilds.find((c) => c.territory_id === id);
    if (parent?.parent_id) {
      if (parent.territory_id !== territory_id) {
        hierarchy.push({
          id: parent.territory_id,
          name: parent.display_name,
        });
      }
      findParent(parent.parent_id);
    } else {
      hierarchy.push({
        id,
        name: parent?.display_name,
      });
    }
  };

  findParent(territory_id);
  const index = hierarchy.findIndex((h) => h.id === territory_id);
  if (index > -1) {
    hierarchy.splice(index, 1);
  }
  return hierarchy;
}

/**
 * Mendapatkan data item group untuk territory
 */
async function getTerritoryItemGroup(hierarchyData) {
  if (hierarchyData.employeeTerritory.length > 0) {
    const territoryId = hierarchyData.employeeTerritory[0].territory.id;
    hierarchyData.territoryItemGroup = await dbSAPPS.TerritoryItemGroup.findAll({
      where: { territory_id: territoryId },
      include: [
        {
          model: dbSAPPS.ItemGroup,
          as: "itemgroup",
          attributes: ["id", "display_name"],
        },
      ],
      attributes: ["territory_id", "itemgroup_id"],
    });

    // Cek jika territoryItemGroup tidak kosong
    if (hierarchyData.territoryItemGroup && hierarchyData.territoryItemGroup.length > 0) {
      // Set itemGroupIds
      hierarchyData.itemGroupIds = hierarchyData.territoryItemGroup.map((t) => t.itemgroup.id);
    }
  }
}

/**
 * Mendapatkan data employee yang memiliki territory parent
 */
async function getEmployeeTerritoryParents(hierarchyData) {
  if (hierarchyData.territoryParent?.length > 0) {
    const parentIds = hierarchyData.territoryParent.map((t) => t.id);

    hierarchyData.employeeTerritoryParent = await dbSAPPS.EmployeeTerritory.findAll({
      attributes: ["employee_id", "territory_id"],
      where: {
        territory_id: {
          [Op.in]: parentIds,
        },
      },
      include: [
        {
          model: dbSAPPS.Employee,
          as: "employee",
          attributes: ["id", "employee_name", "employee_number", "user_id", "designation_id", "use_workflow"],
          include: [
            {
              model: dbSAPPS.User,
              as: "user",
              attributes: ["id", "email", "username", "full_name"],
            },
          ],
        },
        {
          model: dbSAPPS.Territory,
          as: "territory",
          attributes: ["id", "display_name"],
        },
      ],
    });

    if (hierarchyData.employeeTerritoryParent.length > 0) {
      const territoryOrderMap = new Map((hierarchyData.territoryParent || []).map((t, i) => [t.id, i]));
      hierarchyData.employeeTerritoryParent.sort(
        (a, b) => (territoryOrderMap.get(a.territory_id) ?? 999) - (territoryOrderMap.get(b.territory_id) ?? 999)
      );

      hierarchyData.bankEmployeeTerritoryParent = await dbSAPPS.BankAccount.findAll({
        attributes: [
          "id",
          "bank_id",
          "account_number",
          "display_name",
          "name",
          "employee_id",
          "company_id",
          "type",
          "client_id",
          "enabled",
        ],
        where: {
          employee_id: {
            [Op.in]: hierarchyData.employeeTerritoryParent.map((etp) => etp.employee_id),
          },
          enabled: true,
        },
        include: [
          {
            model: dbSAPPS.Bank,
            as: "bank",
            attributes: ["id", "display_name", "code"],
          },
        ],
      });

      hierarchyData.workflowTerritories = hierarchyData.employeeTerritoryParent.map((etp) => ({
        employee_id: etp?.employee_id,
        employee_name: etp?.employee?.employee_name,
        employee_number: etp?.employee?.employee_number,
        designation_id: etp?.employee?.designation_id,
        territory_id: etp?.territory_id,
        territory_name: etp?.territory?.display_name,
        user_id: etp?.employee?.user_id,
        email: etp?.employee?.user?.email,
        username: etp?.employee?.user?.username,
        full_name: etp?.employee?.user?.full_name,
      }));
    }
  }
}

/**
 * Mendapatkan data employee yang memiliki territory child
 */
async function getEmployeeTerritoryChildren(hierarchyData) {
  if (hierarchyData.territoryChild?.length > 0) {
    const getAllTerritoryIds = (arr) => {
      const ids = new Set();
      const addIds = (arr) => {
        for (const item of arr) {
          ids.add(item.id);
          if (item?.children?.length > 0) {
            addIds(item.children);
          }
        }
      };
      addIds(arr);
      return Array.from(ids);
    };

    hierarchyData.territoriesIds = getAllTerritoryIds(hierarchyData.territories);

    hierarchyData.employeeTerritoryChild = await dbSAPPS.EmployeeTerritory.findAll({
      attributes: ["employee_id", "territory_id"],
      where: {
        territory_id: {
          [Op.in]: hierarchyData.territoriesIds,
        },
      },
      include: [
        {
          model: dbSAPPS.Employee,
          as: "employee",
          attributes: ["id", "employee_name", "employee_number", "user_id", "designation_id", "use_workflow"],
          include: [
            {
              model: dbSAPPS.User,
              as: "user",
              attributes: ["id", "email", "username", "full_name"],
            },
          ],
        },
        {
          model: dbSAPPS.Territory,
          as: "territory",
          attributes: ["id", "display_name"],
        },
      ],
    });

    if (hierarchyData.employeeTerritoryChild.length > 0) {
      const territoryOrderMap = new Map((hierarchyData.territoryParent || []).map((t, i) => [t.id, i]));
      hierarchyData.employeeTerritoryChild.sort(
        (a, b) => (territoryOrderMap.get(a.territory_id) ?? 999) - (territoryOrderMap.get(b.territory_id) ?? 999)
      );

      hierarchyData.subCoordinateTerritories = hierarchyData.employeeTerritoryChild.map((etp) => ({
        employee_id: etp.employee_id,
        employee_name: etp.employee.employee_name,
        employee_number: etp.employee.employee_number,
        designation_id: etp.employee.designation_id,
        territory_id: etp.territory_id,
        territory_name: etp.territory.display_name,
        user_id: etp.employee.user_id,
        email: etp.employee.user.email,
        username: etp.employee.user.username,
        full_name: etp.employee.user.full_name,
      }));

      hierarchyData.subCoordinateEmployeeTerritories = hierarchyData.subCoordinateTerritories.map(
        (employee) => employee.employee_id
      );
    }
  }
}

/**
 * Mendapatkan data rekening bank employee
 */
async function getEmployeeBank(user, hierarchyData) {
  if (user?.employee?.id) {
    hierarchyData.employeeBank = await dbSAPPS.BankAccount.findAll({
      where: {
        employee_id: user.employee.id,
        enabled: {
          [Op.eq]: true,
        },
      },
      include: [
        {
          model: dbSAPPS.Bank,
          as: "bank",
          attributes: ["id", "display_name", "code"],
        },
      ],
      attributes: [
        "id",
        "display_name",
        "account_number",
        "branch",
        "type",
        "description",
        "employee_id",
        "bank_id",
        "client_id",
      ],
    });
  }
}

/**
 * Mendapatkan direct parent (1 tingkat di atas)
 */
async function getDirectParent(hierarchyData) {
  if (!hierarchyData.employeeTerritory) return;

  // Cari direct parent dari territory employee
  const directParent = hierarchyData.territoryVersionChilds.find(
    (child) => child.territory_id === hierarchyData.employeeTerritory[0].territory.id
  );

  if (directParent?.parent_id) {
    // Dapatkan informasi territory parent
    const parentTerritory = await dbSAPPS.Territory.findOne({
      attributes: ["id", "display_name"],
      where: { id: directParent.parent_id },
    });

    if (parentTerritory) {
      // Dapatkan employee dengan territory parent tersebut
      const directParentEmployees = await dbSAPPS.EmployeeTerritory.findAll({
        attributes: ["employee_id", "territory_id"],
        where: { territory_id: parentTerritory.id },
        include: [
          {
            model: dbSAPPS.Employee,
            as: "employee",
            attributes: ["id"],
            include: [
              {
                model: dbSAPPS.User,
                as: "user",
                attributes: ["id", "email", "username", "full_name"],
              },
            ],
          },
          {
            model: dbSAPPS.Territory,
            as: "territory",
            attributes: ["id", "display_name"],
          },
        ],
      });

      // Format data workflow direct
      hierarchyData.workflowTerritoryDirect = directParentEmployees.map((employee) => ({
        employee_id: employee.employee_id,
        territory_id: employee.territory_id,
        territory_name: employee.territory.display_name,
        user_id: employee.employee.user?.id,
        email: employee.employee.user?.email,
        username: employee.employee.user?.username,
        full_name: employee.employee.user?.full_name,
      }));
    }
  }
}
