/**
 * usageData.js
 *
 * Data-fetching hooks for ESGAtGlance.
 *
 * Config shape (flat graphConfigs):
 *   {
 *     "ChartKey": {
 *       title, chartType, enableStacking, dataSourceType,
 *       // either:
 *       dataConfigs: [{ label, widgetConfig: { module: ["ModuleName"], filters, group_by, ... } }, ...]
 *       // or:
 *       widgetConfig: { module: ["ModuleName"], filters, group_by, ... }
 *     }
 *   }
 *
 * Module derivation (guaranteed by caller):
 *   dataConfigs  → dataConfigs[0].widgetConfig.module[0]
 *   widgetConfig → widgetConfig.module[0]
 *   All dataConfigs within one chart config share the same module.
 */

import { useState, useEffect, useCallback, useRef } from "react";
import { apiCall } from "../../../../../../_services/apiCall";
import config from "../../../../../../config/config";

const comparisonFieldsMap = {
  "financialYear": "financial_year",
  "locationIds": "location_id",
  "periods": "displayPeriods"
}

export const resolveComparisonValues = (field, currentValue, comparison) => {
  if (!comparison) return [];

  switch (comparison.type) {
    case "previous_period":
      if (field === "financialYear") {
        return [
          currentValue,
          getPreviousFinancialYear(currentValue),
        ];
      }
      return [];

    case "dimension":
      return comparison.values || [];

    default:
      return [];
  }
};

// ─────────────────────────────────────────────────────────────────────────────
// RAW API CALL
// ─────────────────────────────────────────────────────────────────────────────
const fetchPreview = async (payload) => {
  let apiUrl = `${config.POSTLOGIN_API_URL_COMPANY}bi-data/preview`;

  try {
    const { isSuccess, data } = await apiCall(
      apiUrl,
      {},
      payload,
      "POST",
      false,
      false
    );
    if (isSuccess && data?.data) {
      return data.data;
    }
    return [];
  } catch (err) {
    console.error("fetchPreview error:", err);
    return [];
  }
};

const buildKey = (row, fields = []) =>
  fields
    .filter(f => f !== "unit")
    .map(f => row[f])
    .join("|");

const regroupDataset = (dataset, targetGroupBy) => {
  if (!targetGroupBy?.length) return dataset;

  const result = new Map();
  dataset.forEach(row => {
    const key = buildKey(row, targetGroupBy);
    if (result.has(key)) {
      throw new Error(
        `Duplicate key detected for apply_formula_group_by: ${key}`
      );
    }

    result.set(key, row);
  });

  return result;
};

// -------------------------
// FILTER LATEST PERIOD
// -------------------------
const filterLatest = (rows, group_by, displayPeriodOrderMap) => {
  // If displayPeriods is not part of grouping, nothing to filter
  if (!group_by.includes("displayPeriods")) {
    return rows;
  }

  const keyFields = group_by.filter((g) => g !== "displayPeriods");
  const groups = new Map();
  // ── Group rows by key fields
  for (const row of rows) {
    const key = keyFields.map((f) => row[f]).join("|");
    if (!groups.has(key)) {
      groups.set(key, []);
    }
    groups.get(key).push(row);
  }

  const result = [];
  for (const groupRows of groups.values()) {
    // Sort latest → oldest
    const sorted = [...groupRows].sort(
      (a, b) =>
        displayPeriodOrderMap[b.displayPeriods] -
        displayPeriodOrderMap[a.displayPeriods]
    );

    // Pick first row with valid numeric value
    for (const row of sorted) {
      const val = Number(row.sum_value ?? row.value);
      if (Number.isFinite(val)) {
        result.push(row);
        break;
      }
    }
  }

  return result;
};

const fetchProcessedDataPreview = async (dataSourceType, widgetConfig, filters, displayPeriodOrderMap) => {
  const wc = injectFilters(widgetConfig, filters);
  const fetchedRows = await fetchPreview({
    dataSourceType: dataSourceType || "REPORTING",
    widgetConfig: wc
  });

  let processedRows = fetchedRows;
  if (widgetConfig.label) {
    processedRows = fetchedRows.map(d => ({
      ...d,
      [widgetConfig.group_by[0]]: widgetConfig.label,
    }));
  }

  return widgetConfig?.latest ? filterLatest(processedRows, widgetConfig.group_by, displayPeriodOrderMap) : processedRows;
}

const evaluateFormula = (formula, context) => {
  if (!formula) return context.DC1;

  let expr = formula;

  Object.keys(context).forEach((key) => {
    const value = context[key];
    expr = expr.replaceAll(`{{${key}}}`, value);
  });

  try {
    // Safe math-only evaluation
    return Function(`"use strict"; return (${expr})`)();
  } catch (e) {
    console.error("Formula evaluation failed:", expr);
    return 0;
  }
};

const applyAggregate = (values, aggFunc) => {
  switch (aggFunc) {
    case "sum":
      return values.reduce((a, b) => a + b, 0);

    case "avg":
      return values.reduce((a, b) => a + b, 0) / values.length;

    case "count":
      return values.length;

    case "min":
      return Math.min(...values);

    case "max":
      return Math.max(...values);

    default:
      throw new Error(`Unsupported aggregate function: ${aggFunc}`);
  }
};

// ─────────────────────────────────────────────────────────────────────────────
// FILTER INJECTION — deep-clones the widgetConfig and stamps in active filters
// ─────────────────────────────────────────────────────────────────────────────
const injectFilters = (widgetConfig, { financialYear, locationIds, periods }) => {
  const wc = JSON.parse(JSON.stringify(widgetConfig));
  if (financialYear)       wc.filters.financial_year = Array.isArray(financialYear) ? financialYear : [financialYear];
  if (locationIds?.length) wc.filters.location_id     = locationIds;
  if (periods?.length)     wc.filters.displayPeriods  = periods;

  return wc;
};

const executeFormulas = (chartCfg, datasets) => {
  chartCfg?.formulas?.forEach((f,index)=>{
    const resultKey = `FR${index+1}`;
    const groups = new Map();
    const aggObj = f.aggregate || {sum:"value"};
    const aggFunc = Object.keys(aggObj)[0].toLowerCase();
    const extractVars = (formula)=>{
      const matches = formula.match(/{{(.*?)}}/g) || [];
      return matches.map(v=>v.replace(/[{}]/g,""));
    };

    const vars = extractVars(f.formula);

    // regroup datasets for this formula
    const workingDatasets = {};
    vars.forEach(v=>{
      const dataset = datasets[v];
      if(!dataset) return;

      workingDatasets[v] = regroupDataset(dataset, f.apply_formula_group_by);
    });

    const baseKeys = new Set();
    Object.values(workingDatasets).forEach(ds=>{
      ds.forEach((_,key)=>baseKeys.add(key));
    });

    baseKeys.forEach(baseKey => {
      const context = {};
      vars.forEach(v=>{
        const row = workingDatasets[v]?.get(baseKey);
        context[v] = row ? Number(row.sum_value)||0 : 0;
      });

      const evaluated = evaluateFormula(f.formula, context);

      let row = null;
      for(const v of vars){
        const candidate = workingDatasets[v]?.get(baseKey);
        if(candidate){
          row = candidate;
          break;
        }
      }

      if(!row) return;

      const evaluatedRow = {
        ...row,
        unit: chartCfg?.allFormulaUnitsSame && row?.unit ? row.unit : (f.unit ?? ''),
        metric_name: f.metric_name,
        sum_value: evaluated
      };

      const groupKey = buildKey(evaluatedRow, f.group_by);
      if(!groups.has(groupKey)) groups.set(groupKey, []);
      groups.get(groupKey).push(evaluatedRow);
    });

    const resultMap = new Map();
    groups.forEach((rows, key) => {
      const aggregatedValue = applyAggregate(
        rows.map(r => Number(r.sum_value) || 0),
        aggFunc
      );

      const baseRow = {};
      (f.group_by || [])
        .filter(g => g !== "unit")
        .forEach(field => {
          baseRow[field] = rows[0][field];
        });

      resultMap.set(key, {
        ...baseRow,
        metric_name: f.metric_name,
        unit: chartCfg?.allFormulaUnitsSame && rows[0]?.unit ? rows[0].unit : (f.unit ?? ''),
        sum_value: aggregatedValue
      });
    });

    datasets[resultKey] = resultMap;
  });
};

const processDataConfigs = async (chartCfg, filters, displayPeriodOrderMap) => {
  const { dataConfigs } = chartCfg;

  if (!dataConfigs?.length) return [];

  // -------------------------
  // FETCH ALL DATA CONFIGS
  // -------------------------
  const processedSets = await Promise.all(
    dataConfigs.map((dc) => {
      return fetchProcessedDataPreview(chartCfg.dataSourceType, dc.widgetConfig, filters, displayPeriodOrderMap);
    })
  );

  // -------------------------
  // MATCH & APPLY FORMULA
  // -------------------------
  
  // Build DC maps
  const datasets = {};
  processedSets.forEach((rows, i) => {
    datasets[`DC${i+1}`] = rows;
  });

  // Execute formulas sequentially
  executeFormulas(chartCfg, datasets);

  const flattenSources =
    chartCfg.flatten_from?.length
      ? chartCfg.flatten_from
      : chartCfg.dataConfigs.map((_, index) => `DC${index+1}`);

  const finalRows = [];
  flattenSources.forEach((sourceKey) => {
    const dataset = datasets[sourceKey];
    if (!dataset) {
      console.warn(`Flatten source ${sourceKey} not found`);
      return;
    }

    dataset.forEach((row) => {
      finalRows.push({...row});
    });
  });

  return finalRows;
};

// ─────────────────────────────────────────────────────────────────────────────
// MODULE HELPERS
// ─────────────────────────────────────────────────────────────────────────────
export const getChartModule = (chartCfg) =>
  chartCfg?.dataConfigs?.length
    ? chartCfg.dataConfigs[0].widgetConfig.module[0]
    : chartCfg?.widgetConfig?.module?.[0] ?? null;

const getPreviousFinancialYear = (fy) => {
  if (!fy) return null;

  const [start, end] = fy.split('-').map(Number);

  const prevStart = start - 1;
  const prevEnd = end - 1;

  return `${prevStart}-${prevEnd.toString()}`;
};
// ─────────────────────────────────────────────────────────────────────────────
// HOOK: useModuleSummary
// Unchanged shape — still keyed by card.id → aggregated numeric value.
// ─────────────────────────────────────────────────────────────────────────────
export const useModuleSummary = (summaryConfig, filters, displayPeriodOrderMap, isComparisonMode = false, enabled = true) => {
  const [values, setValues]   = useState({});
  const [loading, setLoading] = useState(false);
  const filtersRef            = useRef(null);

  const load = useCallback(async () => {
    if (!enabled || !summaryConfig?.cards?.length || !filters.financialYear) return;

    setLoading(true);
    const results = {};

    await Promise.all(
      summaryConfig.cards.map(async (card) => {
        let updatedFilters = structuredClone(filters);
        let updateCard = structuredClone(card);

        const comparison = card.comparison;
        let currentValueKey = null;
        let previousValueKey = null;

        if (comparison && !isComparisonMode) {
          const field = comparison.field;
          const currentValue = filters[field];

          const [current, previous] = resolveComparisonValues(
            field,
            currentValue,
            comparison
          );

          if (current !== undefined && previous !== undefined) {
            currentValueKey = current;
            previousValueKey = previous;

            if (['financialYear', 'locationIds', 'periods'].includes(field)) {
              updatedFilters = {
                ...updatedFilters,
                [field]: [current, previous],
              };

              updateCard.dataConfigs?.forEach((dc) => {
                const gb = dc?.widgetConfig?.group_by || [];

                if (!gb.includes(comparisonFieldsMap[field])) {
                  dc.widgetConfig.group_by = [...gb, comparisonFieldsMap[field]];
                }
              });

              (updateCard?.formulas ?? []).forEach(f => {
                if (f?.group_by && !f?.group_by?.includes(comparisonFieldsMap[field])) {
                  f.group_by = [...f.group_by, comparisonFieldsMap[field]];
                }
                if (f?.apply_formula_group_by && !f?.apply_formula_group_by?.includes(comparisonFieldsMap[field])) {
                  f.apply_formula_group_by = [...f.apply_formula_group_by, comparisonFieldsMap[field]];
                }
              });
            }
          }
        }

        const rows = await processDataConfigs(updateCard, updatedFilters, displayPeriodOrderMap);

        if (comparison && !isComparisonMode && currentValueKey !== null) {
          let currentValue = 0;
          let prevValue = null;

          rows?.forEach((r) => {

            const comparisonField = comparisonFieldsMap[comparison.field] || comparison.field;

            const key = r[comparisonField];

            if (key === currentValueKey) {
              currentValue += Number(r.sum_value) || 0;
            }

            if (key === previousValueKey) {
              prevValue = (prevValue ?? 0) + (Number(r.sum_value) || 0);
            }
          });

          results[card.id] = {
            current: currentValue,
            previous: prevValue,
            unit: rows[0]?.unit ?? "",
            labels: {
              current: currentValueKey,
              previous: previousValueKey,
            } 
          };
        } else {
          if (rows.length > 1) {
            results[card.id] = null;
          } else {
            results[card.id] = {
              value: (Number(rows[0]?.sum_value) || 0),
              unit: rows[0]?.unit ?? "",
            }
          }
        }
      })
    );
    setValues(results);
    setLoading(false);
  }, [summaryConfig, filters, displayPeriodOrderMap, isComparisonMode, enabled]);

  useEffect(() => {
    const key = JSON.stringify(filters);
    if (filtersRef.current === key) return;
    filtersRef.current = key;
    load();
  }, [load, filters]);

  return { values, loading, reload: load };
};

// ─────────────────────────────────────────────────────────────────────────────
// HOOK: useModuleGraphs  (Financial Year view)
//
// flatGraphConfigs — the full flat { chartKey: chartCfg } from frameworkConfig.
// Filters to only charts whose module matches moduleName.
//
// Returns:
//   chartDataMap: {
//     [chartKey]: {
//       // dataConfigs path — pre-transformed, pass directly to ChartFactory
//       chartData: <ChartFactory-ready object>
//     } | {
//       // widgetConfig path — raw, let ChartFactory transform
//       chartData: { data, chartType, title, dataSourceType },
//       group_by: string[]
//     }
//   }
// ─────────────────────────────────────────────────────────────────────────────
export const useModuleGraphs = (flatGraphConfigs, moduleName, filters, displayPeriodOrderMap, enabled = true) => {
  const [chartDataMap, setChartDataMap] = useState({});
  const [loading, setLoading]           = useState(false);
  const filtersRef                      = useRef(null);

  const load = useCallback(async () => {
    if (!enabled || !flatGraphConfigs || !moduleName || !filters.financialYear) return;

    const myCharts = Object.entries(flatGraphConfigs).filter(
      ([, cfg]) => getChartModule(cfg) === moduleName
    );
    if (!myCharts.length) return;

    setLoading(true);
    const results = {};

    await Promise.all(
      myCharts.map(async ([key, chartCfg]) => {
        // ── Call once per dataConfig, then combine ────────────────────────
        const rows = await processDataConfigs(chartCfg, filters, displayPeriodOrderMap);
        results[key] = {
          chartData: {
            data: rows,
            chartType: chartCfg.chartType,
            title: chartCfg.title,
            dataSourceType: chartCfg.dataSourceType || "REPORTING",
          },
          group_by: chartCfg.group_by,
        };        
      })
    );

    setChartDataMap(results);
    setLoading(false);
  }, [flatGraphConfigs, moduleName, filters, enabled]);

  useEffect(() => {
    const key = JSON.stringify({ moduleName, filters });
    if (filtersRef.current === key) return;
    filtersRef.current = key;
    load();
  }, [load, filters, moduleName]);

  return { chartDataMap, loading, reload: load };
};

// ─────────────────────────────────────────────────────────────────────────────
// HOOK: useComparisonModuleGraphs  (Comparison view)
//
// Same flat config; fetches every chart twice (G1 filters, G2 filters)
// and always returns pre-transformed 2-series data for ChartFactory.
// ─────────────────────────────────────────────────────────────────────────────
export const useComparisonModuleGraphs = (
  flatGraphConfigs, moduleName, comparisonType, group1Filters, group2Filters, displayPeriodOrderMap, enabled = true
) => {
  const [chartDataMap, setChartDataMap] = useState({});
  const [loading, setLoading]           = useState(false);
  const filtersRef                      = useRef(null);

  const load = useCallback(async () => {
    const canRun = enabled && flatGraphConfigs && moduleName &&
      group1Filters?.financialYear && group2Filters?.financialYear;
    if (!canRun) return;

    const myCharts = Object.entries(flatGraphConfigs).filter(
      ([, cfg]) => getChartModule(cfg) === moduleName
    );
    if (!myCharts.length) return;

    setLoading(true);
    const results = {};

    await Promise.all(
      myCharts.map(async ([key, chartCfg]) => {
        // helper to generate label for each group
        const getGroupLabel = (filters, fallback) => {
          if (!filters) return fallback;

          if (comparisonType === "financial_year") {
            return filters.financialYear || fallback;
          }

          if (comparisonType === "location_id") {
            const locations = filters.locationIds || [];
            return locations.length === 1 ? locations[0] : fallback;
          }

          if (comparisonType === "displayPeriods") {
            const periods = filters.periods || [];
            return periods.length === 1 ? periods[0] : fallback;
          }

          return fallback;
        };

        // determine labels
        const g1Label = getGroupLabel(group1Filters, "Group 1");
        const g2Label = getGroupLabel(group2Filters, "Group 2");

        // Fetch every dataConfig for BOTH groups simultaneously
        const [g1Rows, g2Rows] = await Promise.all([
          processDataConfigs(chartCfg, group1Filters, displayPeriodOrderMap),
          processDataConfigs(chartCfg, group2Filters, displayPeriodOrderMap),
        ]);

        // attach filter_group to rows
        const labeledG1 = g1Rows.map(r => ({
          ...r,
          filter_group: g1Label
        }));

        const labeledG2 = g2Rows.map(r => ({
          ...r,
          filter_group: g2Label
        }));

        // replace comparisonType with filter_group
        const updatedGroupBy = chartCfg.group_by.map(g =>
          g === comparisonType ? "filter_group" : g
        );

        results[key] = {
          chartData: {
            data: [...labeledG1, ...labeledG2],
            chartType: chartCfg.groupChartType,
            title: chartCfg.title,
            dataSourceType: chartCfg.dataSourceType || "REPORTING",
          },
          group_by: [...updatedGroupBy, "filter_group"],
        };
      })
    );

    setChartDataMap(results);
    setLoading(false);
  }, [flatGraphConfigs, moduleName, group1Filters, group2Filters, enabled]);

  useEffect(() => {
    const key = JSON.stringify({ moduleName, group1Filters, group2Filters });
    if (filtersRef.current === key) return;
    filtersRef.current = key;
    load();
  }, [load, group1Filters, group2Filters, moduleName]);

  return { chartDataMap, loading, reload: load };
};