import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import {
  canReadAudit,
  canReadLoginHistory,
  fetchRolePermissions,
  ROLE_LABELS,
} from "../utils/permissions.js";

const cleanString = (value) => String(value ?? "").trim();

const inferAuditActionType = (action) => {
  const normalized = String(action || "").toLowerCase();

  if (normalized.includes("login")) {
    return "LOGIN";
  }

  if (normalized.includes("close") || normalized.includes("closure")) {
    return "CLOSE";
  }

  if (
    normalized.includes("delete") ||
    normalized.includes("remove") ||
    normalized.includes("deactivate")
  ) {
    return "DELETE";
  }

  if (normalized.includes("create") || normalized.includes("import")) {
    return "CREATE";
  }

  return "UPDATE";
};

const inferModule = (action, entityType) => {
  const actionPrefix = String(action || "").split(".")[0];

  if (actionPrefix && actionPrefix !== action) {
    return actionPrefix;
  }

  return entityType || "system";
};

const buildAuditDetail = (oldData, newData) => {
  const previous = oldData && typeof oldData === "object" ? oldData : {};
  const next = newData && typeof newData === "object" ? newData : {};
  const keys = [...new Set([...Object.keys(previous), ...Object.keys(next)])];
  const changedKeys = keys.filter(
    (key) => JSON.stringify(previous[key] ?? null) !== JSON.stringify(next[key] ?? null),
  );

  if (changedKeys.length === 0) {
    return "Aucune différence détaillée.";
  }

  return changedKeys.map((key) => key).join(", ");
};

const parseIsoDate = (value) => {
  const cleaned = cleanString(value);
  return cleaned || null;
};

const ensureAuditPermission = async (req, mode) => {
  const permissions = await fetchRolePermissions(pool, req.user?.role);
  const allowed =
    mode === "audit"
      ? canReadAudit(req.user?.role, permissions)
      : canReadLoginHistory(req.user?.role, permissions);

  if (!allowed) {
    throw new ApiError(403, "Accès refusé aux journaux d'administration.");
  }
};

export const adminAuditController = {
  auditLog: async (req, res) => {
    await ensureAuditPermission(req, "audit");

    const companyId = req.query.company_id ? Number(req.query.company_id) : null;
    const userId = req.query.user_id ? Number(req.query.user_id) : null;
    const from = parseIsoDate(req.query.from);
    const to = parseIsoDate(req.query.to);
    const action = cleanString(req.query.action);
    const moduleKey = cleanString(req.query.module);

    const { rows } = await pool.query(
      `
        SELECT
          al.id,
          al.company_id,
          al.user_id,
          al.action,
          al.entity_type,
          al.entity_id,
          al.old_data,
          al.new_data,
          al.ip_address,
          al.created_at,
          u.full_name AS user_full_name,
          u.email AS user_email
        FROM audit_log al
        LEFT JOIN users u
          ON u.id = al.user_id
        WHERE ($1::bigint IS NULL OR al.company_id = $1)
          AND ($2::bigint IS NULL OR al.user_id = $2)
          AND ($3::timestamptz IS NULL OR al.created_at >= $3::timestamptz)
          AND ($4::timestamptz IS NULL OR al.created_at < ($4::date + INTERVAL '1 day'))
          AND ($5::text = '' OR al.action = $5)
          AND (
            $6::text = ''
            OR split_part(al.action, '.', 1) = $6
            OR al.entity_type = $6
          )
        ORDER BY al.created_at DESC
        LIMIT 500
      `,
      [companyId, userId, from, to, action, moduleKey],
    );

    const { rows: users } = await pool.query(
      `
        SELECT DISTINCT
          u.id,
          u.full_name
        FROM audit_log al
        JOIN users u
          ON u.id = al.user_id
        ORDER BY u.full_name ASC
      `,
    );

    res.json({
      items: rows.map((row) => ({
        id: Number(row.id),
        company_id: row.company_id ? Number(row.company_id) : null,
        user_id: row.user_id ? Number(row.user_id) : null,
        user_name: row.user_full_name || row.user_email || "Système",
        action: row.action,
        action_type: inferAuditActionType(row.action),
        module: inferModule(row.action, row.entity_type),
        entity_type: row.entity_type,
        entity_id: row.entity_id ? Number(row.entity_id) : null,
        entity_label: row.entity_id ? `${row.entity_type} #${row.entity_id}` : row.entity_type,
        ip_address: row.ip_address || null,
        detail: buildAuditDetail(row.old_data, row.new_data),
        old_data: row.old_data,
        new_data: row.new_data,
        created_at: row.created_at ? new Date(row.created_at).toISOString() : null,
      })),
      filters: {
        users: users.map((row) => ({
          id: Number(row.id),
          full_name: row.full_name,
        })),
      },
    });
  },

  loginHistory: async (req, res) => {
    await ensureAuditPermission(req, "login_history");

    const companyId = req.query.company_id ? Number(req.query.company_id) : null;

    const { rows } = await pool.query(
      `
        SELECT
          lh.id,
          lh.company_id,
          lh.user_id,
          lh.email,
          lh.ip_address,
          lh.user_agent,
          lh.browser,
          lh.result,
          lh.reason,
          lh.attempted_at,
          u.full_name,
          u.role
        FROM login_history lh
        LEFT JOIN users u
          ON u.id = lh.user_id
        WHERE ($1::bigint IS NULL OR lh.company_id = $1 OR lh.company_id IS NULL)
        ORDER BY lh.attempted_at DESC
        LIMIT 500
      `,
      [companyId],
    );

    res.json({
      items: rows.map((row) => ({
        id: Number(row.id),
        company_id: row.company_id ? Number(row.company_id) : null,
        user_id: row.user_id ? Number(row.user_id) : null,
        user_name: row.full_name || row.email || "Inconnu",
        email: row.email,
        role_label: row.role ? ROLE_LABELS[row.role] || row.role : null,
        ip_address: row.ip_address || null,
        browser: row.browser || "Inconnu",
        user_agent: row.user_agent || "",
        result: row.result,
        reason: row.reason || null,
        attempted_at: row.attempted_at ? new Date(row.attempted_at).toISOString() : null,
      })),
    });
  },
};
