import { pool } from "../db/pool.js";
import { ApiError } from "../utils/ApiError.js";
import { logAudit } from "../utils/auditLog.js";

const parseInteger = (value, fallback) => {
  const parsed = Number.parseInt(value, 10);
  return Number.isFinite(parsed) ? parsed : fallback;
};

const ensureMobileNotificationsTable = async () => {
  await pool.query(`
    CREATE TABLE IF NOT EXISTS mgx_mobile_notifications (
      id BIGSERIAL PRIMARY KEY,
      mobile_account_id BIGINT NOT NULL REFERENCES mgx_mobile_accounts (id) ON DELETE CASCADE,
      type VARCHAR(80),
      message TEXT NOT NULL,
      is_read BOOLEAN NOT NULL DEFAULT FALSE,
      entity_id BIGINT,
      created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
    )
  `);
};

export const notificationsController = {
  list: async (req, res) => {
    const isMobileAccount = req.user?.accountType === "mgx_mobile_account";
    const userId = isMobileAccount ? null : Number(req.user?.sub || 0);
    const mobileAccountId = isMobileAccount
      ? Number(req.user?.mobileAccountId || req.user?.mobile_account_id || req.user?.sub || 0)
      : null;
    const limit = Math.min(parseInteger(req.query.limit, 30), 100);
    const unreadOnly = ["true", "1", "oui", "yes"].includes(
      String(req.query.unread_only || "").toLowerCase(),
    );

    if (!userId && !mobileAccountId) {
      throw new ApiError(401, "Authentication required.");
    }

    if (isMobileAccount) {
      await ensureMobileNotificationsTable();

      const values = [mobileAccountId];
      let conditions = "WHERE mobile_account_id = $1";
      if (unreadOnly) conditions += " AND is_read = FALSE";
      values.push(limit);

      const [itemsResponse, unreadResponse] = await Promise.all([
        pool.query(
          `
            SELECT id, mobile_account_id, type, message, is_read, entity_id,
                   TO_CHAR(created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS created_at
            FROM mgx_mobile_notifications
            ${conditions}
            ORDER BY created_at DESC
            LIMIT $${values.length}
          `,
          values,
        ),
        pool.query(
          `
            SELECT COUNT(*)::int AS unread_count
            FROM mgx_mobile_notifications
            WHERE mobile_account_id = $1
              AND is_read = FALSE
          `,
          [mobileAccountId],
        ),
      ]);

      res.json({
        items: itemsResponse.rows.map((row) => ({
          id: Number(row.id),
          mobile_account_id: Number(row.mobile_account_id),
          type: row.type,
          message: row.message,
          is_read: row.is_read,
          entity_id: row.entity_id === null ? null : Number(row.entity_id),
          created_at: row.created_at,
        })),
        unread_count: Number(unreadResponse.rows[0]?.unread_count || 0),
      });
      return;
    }

    const values = [userId];
    let conditions = "WHERE n.user_id = $1";

    if (unreadOnly) {
      conditions += " AND n.is_read = FALSE";
    }

    values.push(limit);

    const [itemsResponse, unreadResponse] = await Promise.all([
      pool.query(
        `
          SELECT
            n.id,
            n.user_id,
            n.type,
            n.message,
            n.is_read,
            n.entity_id,
            TO_CHAR(n.created_at AT TIME ZONE 'UTC', 'YYYY-MM-DD"T"HH24:MI:SS"Z"') AS created_at
          FROM notifications n
          ${conditions}
          ORDER BY n.created_at DESC
          LIMIT $${values.length}
        `,
        values,
      ),
      pool.query(
        `
          SELECT COUNT(*)::int AS unread_count
          FROM notifications
          WHERE user_id = $1
            AND is_read = FALSE
        `,
        [userId],
      ),
    ]);

    res.json({
      items: itemsResponse.rows.map((row) => ({
        id: Number(row.id),
        user_id: Number(row.user_id),
        type: row.type,
        message: row.message,
        is_read: row.is_read,
        entity_id: row.entity_id === null ? null : Number(row.entity_id),
        created_at: row.created_at,
      })),
      unread_count: Number(unreadResponse.rows[0]?.unread_count || 0),
    });
  },

  markRead: async (req, res) => {
    const isMobileAccount = req.user?.accountType === "mgx_mobile_account";
    const userId = isMobileAccount ? null : Number(req.user?.sub || 0);
    const mobileAccountId = isMobileAccount
      ? Number(req.user?.mobileAccountId || req.user?.mobile_account_id || req.user?.sub || 0)
      : null;
    const notificationId = Number(req.params.id);

    if ((!userId && !mobileAccountId) || !notificationId) {
      throw new ApiError(400, "Notification invalide.");
    }

    if (isMobileAccount) {
      await ensureMobileNotificationsTable();
      const { rows } = await pool.query(
        `
          UPDATE mgx_mobile_notifications
          SET is_read = TRUE
          WHERE id = $1
            AND mobile_account_id = $2
          RETURNING id, mobile_account_id, type, message, is_read, entity_id
        `,
        [notificationId, mobileAccountId],
      );
      if (!rows[0]) throw new ApiError(404, "Notification introuvable.");
      res.json({ item: rows[0] });
      return;
    }

    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      const { rows } = await client.query(
        `
          UPDATE notifications
          SET is_read = TRUE
          WHERE id = $1
            AND user_id = $2
          RETURNING id, user_id, type, message, is_read, entity_id
        `,
        [notificationId, userId],
      );

      if (!rows[0]) {
        throw new ApiError(404, "Notification introuvable.");
      }

      await logAudit(client, {
        companyId: req.user?.companyId || null,
        userId,
        action: "notifications.read",
        entityType: "notification",
        entityId: notificationId,
        newData: {
          is_read: true,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        item: {
          id: Number(rows[0].id),
          user_id: Number(rows[0].user_id),
          type: rows[0].type,
          message: rows[0].message,
          is_read: rows[0].is_read,
          entity_id: rows[0].entity_id === null ? null : Number(rows[0].entity_id),
        },
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  markAllRead: async (req, res) => {
    const isMobileAccount = req.user?.accountType === "mgx_mobile_account";
    const userId = isMobileAccount ? null : Number(req.user?.sub || 0);
    const mobileAccountId = isMobileAccount
      ? Number(req.user?.mobileAccountId || req.user?.mobile_account_id || req.user?.sub || 0)
      : null;

    if (!userId && !mobileAccountId) {
      throw new ApiError(401, "Authentication required.");
    }

    if (isMobileAccount) {
      await ensureMobileNotificationsTable();
      const { rowCount } = await pool.query(
        `
          UPDATE mgx_mobile_notifications
          SET is_read = TRUE
          WHERE mobile_account_id = $1
            AND is_read = FALSE
        `,
        [mobileAccountId],
      );
      res.json({ updated_count: rowCount });
      return;
    }

    const client = await pool.connect();

    try {
      await client.query("BEGIN");

      const { rowCount } = await client.query(
        `
          UPDATE notifications
          SET is_read = TRUE
          WHERE user_id = $1
            AND is_read = FALSE
        `,
        [userId],
      );

      await logAudit(client, {
        companyId: req.user?.companyId || null,
        userId,
        action: "notifications.read_all",
        entityType: "notification",
        newData: {
          updated_count: rowCount,
        },
        ipAddress: req.ip,
      });

      await client.query("COMMIT");

      res.json({
        updated_count: rowCount,
      });
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },

  remove: async (req, res) => {
    const isMobileAccount = req.user?.accountType === "mgx_mobile_account";
    const userId = isMobileAccount ? null : Number(req.user?.sub || 0);
    const mobileAccountId = isMobileAccount
      ? Number(req.user?.mobileAccountId || req.user?.mobile_account_id || req.user?.sub || 0)
      : null;
    const notificationId = Number(req.params.id);

    if ((!userId && !mobileAccountId) || !notificationId) {
      throw new ApiError(400, "Notification invalide.");
    }

    if (isMobileAccount) {
      await ensureMobileNotificationsTable();
      const { rowCount } = await pool.query(
        `
          DELETE FROM mgx_mobile_notifications
          WHERE id = $1
            AND mobile_account_id = $2
        `,
        [notificationId, mobileAccountId],
      );
      if (!rowCount) throw new ApiError(404, "Notification introuvable.");
      res.status(204).send();
      return;
    }

    const { rowCount } = await pool.query(
      `
        DELETE FROM notifications
        WHERE id = $1
          AND user_id = $2
      `,
      [notificationId, userId],
    );

    if (!rowCount) {
      throw new ApiError(404, "Notification introuvable.");
    }

    res.status(204).send();
  },
};
