import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { formatDzdAmount, toNumber } from "../utils/formatters.js";

const assetCondition = `
  (
    a.account_type IN ('fixed_asset', 'financial_asset', 'inventory', 'treasury')
    OR (a.code LIKE '4%' AND a.balance_normal_side = 'debit')
  )
`;

const liabilityCondition = `
  (
    a.account_type IN ('liability', 'equity')
    OR (a.code LIKE '4%' AND a.balance_normal_side = 'credit')
  )
`;

const monthLabels = [
  "Jan",
  "Feb",
  "Mar",
  "Apr",
  "May",
  "Jun",
  "Jul",
  "Aug",
  "Sep",
  "Oct",
  "Nov",
  "Dec",
];

const resolveDashboardScope = async (req) => {
  const companyId = Number(req.query.company_id || req.user?.companyId || 0);

  if (!companyId) {
    throw new ApiError(400, "company_id is required.");
  }

  let fiscalYearId = req.query.fiscal_year_id ? Number(req.query.fiscal_year_id) : null;

  if (!fiscalYearId) {
    const { rows } = await pool.query(
      `
        SELECT id
        FROM fiscal_years
        WHERE company_id = $1
        ORDER BY is_current DESC, start_date DESC
        LIMIT 1
      `,
      [companyId],
    );

    fiscalYearId = rows[0]?.id ? Number(rows[0].id) : null;
  }

  return {
    companyId,
    fiscalYearId,
  };
};

const buildTrend = (current, previous) => {
  if (!previous && !current) {
    return {
      direction: "neutral",
      percentage: null,
    };
  }

  if (!previous) {
    return {
      direction: current >= 0 ? "up" : "down",
      percentage: null,
    };
  }

  const delta = current - previous;
  const percentage = (delta / Math.abs(previous)) * 100;

  if (Math.abs(percentage) < 0.05) {
    return {
      direction: "neutral",
      percentage: 0,
    };
  }

  return {
    direction: percentage >= 0 ? "up" : "down",
    percentage: Number(percentage.toFixed(1)),
  };
};

const getKpiMetrics = async (companyId, fiscalYearId) => {
  const { rows } = await pool.query(
    `
      WITH filtered_lines AS (
        SELECT
          el.account_id,
          el.debit,
          el.credit
        FROM entry_lines el
        JOIN journal_entries je
          ON je.id = el.entry_id
        WHERE je.company_id = $1
          AND je.status = 'validated'
          AND ($2::bigint IS NULL OR je.fiscal_year_id = $2)
      ),
      account_balances AS (
        SELECT
          a.id,
          a.code,
          a.account_type,
          a.balance_normal_side,
          COALESCE(SUM(fl.debit), 0)::numeric AS total_debit,
          COALESCE(SUM(fl.credit), 0)::numeric AS total_credit
        FROM accounts a
        LEFT JOIN filtered_lines fl
          ON fl.account_id = a.id
        WHERE a.company_id = $1
        GROUP BY a.id, a.code, a.account_type, a.balance_normal_side
      )
      SELECT
        COALESCE(SUM(
          CASE
            WHEN ${assetCondition}
              THEN GREATEST(total_debit - total_credit, 0)
            ELSE 0
          END
        ), 0)::numeric AS total_actif,
        COALESCE(SUM(
          CASE
            WHEN ${liabilityCondition}
              THEN GREATEST(total_credit - total_debit, 0)
            ELSE 0
          END
        ), 0)::numeric AS total_passif,
        COALESCE(SUM(
          CASE
            WHEN code LIKE '7%'
              THEN GREATEST(total_credit - total_debit, 0)
            ELSE 0
          END
        ), 0)::numeric AS chiffre_affaires_ytd,
        (
          COALESCE(SUM(
            CASE
              WHEN code LIKE '7%'
                THEN GREATEST(total_credit - total_debit, 0)
              ELSE 0
            END
          ), 0)
          -
          COALESCE(SUM(
            CASE
              WHEN code LIKE '6%'
                THEN GREATEST(total_debit - total_credit, 0)
              ELSE 0
            END
          ), 0)
        )::numeric AS resultat_net
      FROM account_balances a
    `,
    [companyId, fiscalYearId],
  );

  return rows[0];
};

const getKpiTrendSource = async (companyId, fiscalYearId) => {
  const { rows } = await pool.query(
    `
      SELECT
        EXTRACT(MONTH FROM je.entry_date)::int AS month_index,
        COALESCE(SUM(
          CASE
            WHEN ${assetCondition}
              THEN el.debit - el.credit
            ELSE 0
          END
        ), 0)::numeric AS asset_movement,
        COALESCE(SUM(
          CASE
            WHEN ${liabilityCondition}
              THEN el.credit - el.debit
            ELSE 0
          END
        ), 0)::numeric AS liability_movement,
        COALESCE(SUM(
          CASE
            WHEN a.code LIKE '7%'
              THEN el.credit - el.debit
            ELSE 0
          END
        ), 0)::numeric AS revenue_movement,
        (
          COALESCE(SUM(
            CASE
              WHEN a.code LIKE '7%'
                THEN el.credit - el.debit
              ELSE 0
            END
          ), 0)
          -
          COALESCE(SUM(
            CASE
              WHEN a.code LIKE '6%'
                THEN el.debit - el.credit
              ELSE 0
            END
          ), 0)
        )::numeric AS net_movement
      FROM journal_entries je
      JOIN entry_lines el
        ON el.entry_id = je.id
      JOIN accounts a
        ON a.id = el.account_id
      WHERE je.company_id = $1
        AND je.status = 'validated'
        AND ($2::bigint IS NULL OR je.fiscal_year_id = $2)
      GROUP BY 1
      ORDER BY 1
    `,
    [companyId, fiscalYearId],
  );

  const orderedRows = rows.map((row) => ({
    monthIndex: Number(row.month_index),
    assetMovement: toNumber(row.asset_movement),
    liabilityMovement: toNumber(row.liability_movement),
    revenueMovement: toNumber(row.revenue_movement),
    netMovement: toNumber(row.net_movement),
  }));

  const currentMonth = orderedRows.at(-1) || {
    assetMovement: 0,
    liabilityMovement: 0,
    revenueMovement: 0,
    netMovement: 0,
  };
  const previousMonth = orderedRows.at(-2) || {
    assetMovement: 0,
    liabilityMovement: 0,
    revenueMovement: 0,
    netMovement: 0,
  };

  return {
    total_actif: buildTrend(currentMonth.assetMovement, previousMonth.assetMovement),
    total_passif: buildTrend(currentMonth.liabilityMovement, previousMonth.liabilityMovement),
    chiffre_affaires_ytd: buildTrend(
      currentMonth.revenueMovement,
      previousMonth.revenueMovement,
    ),
    resultat_net: buildTrend(currentMonth.netMovement, previousMonth.netMovement),
  };
};

const buildKpiItem = (key, value, trend) => ({
  key,
  value,
  formatted_value: formatDzdAmount(value),
  trend_direction: trend.direction,
  trend_percentage: trend.percentage,
});

const getMonthlyChartItems = async (companyId, fiscalYearId) => {
  const { rows } = await pool.query(
    `
      WITH monthly_totals AS (
        SELECT
          EXTRACT(MONTH FROM je.entry_date)::int AS month_index,
          COALESCE(SUM(el.debit), 0)::numeric AS debit_total,
          COALESCE(SUM(el.credit), 0)::numeric AS credit_total
        FROM journal_entries je
        JOIN entry_lines el
          ON el.entry_id = je.id
        WHERE je.company_id = $1
          AND je.status = 'validated'
          AND ($2::bigint IS NULL OR je.fiscal_year_id = $2)
        GROUP BY 1
      )
      SELECT
        months.month_index,
        COALESCE(monthly_totals.debit_total, 0)::numeric AS debit_total,
        COALESCE(monthly_totals.credit_total, 0)::numeric AS credit_total
      FROM generate_series(1, 12) AS months(month_index)
      LEFT JOIN monthly_totals
        ON monthly_totals.month_index = months.month_index
      ORDER BY months.month_index
    `,
    [companyId, fiscalYearId],
  );

  return rows.map((row) => {
    const debit = toNumber(row.debit_total);
    const credit = toNumber(row.credit_total);
    const monthIndex = Number(row.month_index);

    return {
      month_index: monthIndex,
      month_label: monthLabels[monthIndex - 1],
      debit,
      credit,
      formatted_debit: formatDzdAmount(debit),
      formatted_credit: formatDzdAmount(credit),
    };
  });
};

const getJournalDistributionItems = async (companyId, fiscalYearId) => {
  const { rows } = await pool.query(
    `
      SELECT
        j.journal_type,
        COUNT(je.id)::int AS entry_count
      FROM journals j
      LEFT JOIN journal_entries je
        ON je.journal_id = j.id
        AND je.company_id = $1
        AND je.status <> 'cancelled'
        AND ($2::bigint IS NULL OR je.fiscal_year_id = $2)
      WHERE j.company_id = $1
      GROUP BY j.journal_type
      ORDER BY entry_count DESC, j.journal_type ASC
    `,
    [companyId, fiscalYearId],
  );

  return rows.map((row) => ({
    journal_type: row.journal_type,
    entry_count: Number(row.entry_count),
  }));
};

const getRecentEntriesItems = async (companyId, limit) => {
  const { rows } = await pool.query(
    `
      SELECT
        je.id,
        je.entry_date,
        COALESCE(NULLIF(je.piece_ref, ''), je.entry_number) AS piece_number,
        j.code AS journal_code,
        j.label AS journal_label,
        je.label,
        je.status,
        COALESCE(SUM(el.debit), 0)::numeric AS amount
      FROM journal_entries je
      JOIN journals j
        ON j.id = je.journal_id
      LEFT JOIN entry_lines el
        ON el.entry_id = je.id
      WHERE je.company_id = $1
        AND je.status = 'validated'
      GROUP BY je.id, je.entry_date, piece_number, j.code, j.label, je.label, je.status
      ORDER BY je.entry_date DESC, je.updated_at DESC
      LIMIT $2
    `,
    [companyId, limit],
  );

  return rows.map((row) => {
    const amount = toNumber(row.amount);

    return {
      id: Number(row.id),
      entry_date: row.entry_date,
      piece_number: row.piece_number,
      journal_code: row.journal_code,
      journal_label: row.journal_label,
      label: row.label,
      amount,
      formatted_amount: formatDzdAmount(amount),
      status: row.status,
    };
  });
};

const getAlertsItems = async (companyId) => {
  const { rows } = await pool.query(
    `
      WITH current_fiscal_year AS (
        SELECT id
        FROM fiscal_years
        WHERE company_id = $1
        ORDER BY is_current DESC, start_date DESC
        LIMIT 1
      )
      SELECT
        (
          SELECT COUNT(*)
          FROM accounting_periods
          WHERE company_id = $1
            AND is_closed = FALSE
            AND (
              (SELECT id FROM current_fiscal_year) IS NULL
              OR fiscal_year_id = (SELECT id FROM current_fiscal_year)
            )
        )::int AS periods_not_closed,
        (
          SELECT COUNT(*)
          FROM entry_lines el
          JOIN journal_entries je
            ON je.id = el.entry_id
          WHERE je.company_id = $1
            AND je.status = 'validated'
            AND el.auxiliary_account_id IS NOT NULL
            AND COALESCE(el.is_lettered, FALSE) = FALSE
        )::int AS unmatched_entries,
        (
          SELECT COUNT(*)
          FROM bank_movements
          WHERE company_id = $1
            AND COALESCE(is_reconciled, FALSE) = FALSE
        )::int AS unreconciled_bank_movements,
        (
          SELECT COUNT(*)
          FROM assets a
          WHERE a.company_id = $1
            AND a.status = 'active'
            AND a.acquisition_date <= (DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month - 1 day')::date
            AND NOT EXISTS (
              SELECT 1
              FROM depreciation_lines dl
              WHERE dl.asset_id = a.id
                AND DATE_TRUNC('month', dl.period_date) = DATE_TRUNC('month', CURRENT_DATE)
            )
        )::int AS assets_to_depreciate
    `,
    [companyId],
  );

  const result = rows[0];

  return [
    {
      key: "periods_not_closed",
      count: Number(result.periods_not_closed),
      severity: "warning",
      route: "/cloture",
    },
    {
      key: "unmatched_entries",
      count: Number(result.unmatched_entries),
      severity: "warning",
      route: "/lettrage",
    },
    {
      key: "unreconciled_bank_movements",
      count: Number(result.unreconciled_bank_movements),
      severity: "danger",
      route: "/tresorerie/banque",
    },
    {
      key: "assets_to_depreciate",
      count: Number(result.assets_to_depreciate),
      severity: "info",
      route: "/assets",
    },
  ];
};

const getTreasuryBalanceItems = async (companyId) => {
  const { rows } = await pool.query(
    `
      SELECT
        id,
        'bank' AS source_type,
        bank_name || ' - ' || account_number AS label,
        current_balance::numeric AS balance
      FROM bank_accounts
      WHERE company_id = $1
        AND is_active = TRUE
      UNION ALL
      SELECT
        id,
        'cash' AS source_type,
        name AS label,
        current_balance::numeric AS balance
      FROM cash_registers
      WHERE company_id = $1
        AND is_active = TRUE
      ORDER BY source_type, label
    `,
    [companyId],
  );

  return rows.map((row) => {
    const balance = toNumber(row.balance);

    return {
      id: Number(row.id),
      source_type: row.source_type,
      label: row.label,
      balance,
      formatted_balance: formatDzdAmount(balance),
    };
  });
};

const getRapidBalanceItems = async (companyId, fiscalYearId, limit) => {
  const { rows } = await pool.query(
    `
      WITH filtered_lines AS (
        SELECT
          el.account_id,
          el.debit,
          el.credit
        FROM entry_lines el
        JOIN journal_entries je
          ON je.id = el.entry_id
        WHERE je.company_id = $1
          AND je.status = 'validated'
          AND ($2::bigint IS NULL OR je.fiscal_year_id = $2)
      )
      SELECT
        a.id,
        a.code,
        a.label,
        COALESCE(SUM(fl.debit + fl.credit), 0)::numeric AS movement_volume,
        CASE
          WHEN a.balance_normal_side = 'debit'
            THEN COALESCE(SUM(fl.debit - fl.credit), 0)
          ELSE COALESCE(SUM(fl.credit - fl.debit), 0)
        END::numeric AS balance
      FROM accounts a
      LEFT JOIN filtered_lines fl
        ON fl.account_id = a.id
      WHERE a.company_id = $1
      GROUP BY a.id, a.code, a.label, a.balance_normal_side
      HAVING COALESCE(SUM(fl.debit + fl.credit), 0) > 0
      ORDER BY movement_volume DESC, a.code ASC
      LIMIT $3
    `,
    [companyId, fiscalYearId, limit],
  );

  return rows.map((row) => {
    const balance = toNumber(row.balance);
    const movementVolume = toNumber(row.movement_volume);

    return {
      id: Number(row.id),
      code: row.code,
      label: row.label,
      balance,
      formatted_balance: formatDzdAmount(balance),
      movement_volume: movementVolume,
      formatted_movement_volume: formatDzdAmount(movementVolume),
    };
  });
};

const getActivityItems = async (companyId, limit) => {
  const { rows } = await pool.query(
    `
      SELECT
        al.id,
        COALESCE(u.full_name, 'Système') AS user_name,
        al.action,
        al.entity_type,
        al.created_at
      FROM audit_log al
      LEFT JOIN users u
        ON u.id = al.user_id
      WHERE al.company_id = $1
      ORDER BY al.created_at DESC
      LIMIT $2
    `,
    [companyId, limit],
  );

  return rows.map((row) => ({
    id: Number(row.id),
    user_name: row.user_name,
    action: row.action,
    entity_type: row.entity_type,
    created_at: row.created_at,
  }));
};

export const dashboardController = {
  getKpis: async (req, res) => {
    const { companyId, fiscalYearId } = await resolveDashboardScope(req);
    const metrics = await getKpiMetrics(companyId, fiscalYearId);
    const trends = await getKpiTrendSource(companyId, fiscalYearId);

    res.json({
      company_id: companyId,
      fiscal_year_id: fiscalYearId,
      items: [
        buildKpiItem("total_actif", toNumber(metrics.total_actif), trends.total_actif),
        buildKpiItem("total_passif", toNumber(metrics.total_passif), trends.total_passif),
        buildKpiItem(
          "chiffre_affaires_ytd",
          toNumber(metrics.chiffre_affaires_ytd),
          trends.chiffre_affaires_ytd,
        ),
        buildKpiItem("resultat_net", toNumber(metrics.resultat_net), trends.resultat_net),
      ],
    });
  },

  getMonthlyChart: async (req, res) => {
    const { companyId, fiscalYearId } = await resolveDashboardScope(req);

    res.json({
      company_id: companyId,
      fiscal_year_id: fiscalYearId,
      items: await getMonthlyChartItems(companyId, fiscalYearId),
    });
  },

  getJournalDistribution: async (req, res) => {
    const { companyId, fiscalYearId } = await resolveDashboardScope(req);

    res.json({
      company_id: companyId,
      fiscal_year_id: fiscalYearId,
      items: await getJournalDistributionItems(companyId, fiscalYearId),
    });
  },

  getRecentEntries: async (req, res) => {
    const companyId = Number(req.query.company_id || req.user?.companyId || 0);
    const limit = Number(req.query.limit || 10);

    if (!companyId) {
      throw new ApiError(400, "company_id is required.");
    }

    res.json({
      company_id: companyId,
      items: await getRecentEntriesItems(companyId, limit),
    });
  },

  getAlerts: async (req, res) => {
    const companyId = Number(req.query.company_id || req.user?.companyId || 0);

    if (!companyId) {
      throw new ApiError(400, "company_id is required.");
    }

    res.json({
      company_id: companyId,
      items: await getAlertsItems(companyId),
    });
  },

  getTreasuryBalances: async (req, res) => {
    const companyId = Number(req.query.company_id || req.user?.companyId || 0);

    if (!companyId) {
      throw new ApiError(400, "company_id is required.");
    }

    res.json({
      company_id: companyId,
      items: await getTreasuryBalanceItems(companyId),
    });
  },

  getRapidBalance: async (req, res) => {
    const { companyId, fiscalYearId } = await resolveDashboardScope(req);
    const limit = Number(req.query.limit || 10);

    res.json({
      company_id: companyId,
      fiscal_year_id: fiscalYearId,
      items: await getRapidBalanceItems(companyId, fiscalYearId, limit),
    });
  },

  getActivity: async (req, res) => {
    const companyId = Number(req.query.company_id || req.user?.companyId || 0);
    const limit = Number(req.query.limit || 10);

    if (!companyId) {
      throw new ApiError(400, "company_id is required.");
    }

    res.json({
      company_id: companyId,
      items: await getActivityItems(companyId, limit),
    });
  },
};
