import express from "express";
import { z } from "zod";
import { authenticate, requireMfa } from "../../middleware/auth.js";
import { requirePermission, requireRoles } from "../../middleware/rbac.js";
import { validate } from "../../middleware/validate.js";
import { withDbSession } from "../../config/db.js";
import { writeAudit } from "../../utils/audit.js";

const router = express.Router();
router.use(authenticate);

const nullablePositiveInt = z.preprocess(
  (value) => {
    if (value === "" || value === null || value === undefined) return undefined;
    return value;
  },
  z.coerce.number().int().positive().optional()
);

const createSchema = z.object({
  name: z.string().min(2).max(255),
  committee_type: z.string().min(2).max(100).default("general"),
  default_user_id: nullablePositiveInt,
  chair_user_id: nullablePositiveInt,
  secretary_user_id: nullablePositiveInt,
});

const updateSchema = createSchema.partial();

const assignMemberSchema = z.object({
  user_id: z.coerce.number().int().positive(),
});

function normalizeNullableText(value) {
  if (value === undefined) return undefined;
  if (value === null) return null;
  const trimmed = String(value).trim();
  return trimmed ? trimmed : null;
}

function normalizeNullableNumber(value) {
  if (value === undefined) return undefined;
  if (value === null || value === "") return null;
  return Number(value);
}

function mapCommitteeRow(row) {
  if (!row) return null;
  return {
    id: row.id,
    name: row.name,
    committee_type: row.committee_type,
    default_user_id: row.default_user_id,
    default_user_name: row.default_user_name,
    chair_user_id: row.chair_user_id,
    chair_user_name: row.chair_user_name,
    secretary_user_id: row.secretary_user_id,
    secretary_user_name: row.secretary_user_name,
    member_count: row.member_count ?? 0,
    created_at: row.created_at ?? null,
    updated_at: row.updated_at ?? null,
  };
}

async function getTableColumns(db, tableName) {
  const result = await db.query(
    `
      SELECT column_name
        FROM information_schema.columns
       WHERE table_schema = current_schema()
         AND table_name = $1
    `,
    [tableName]
  );

  return new Set(result.rows.map((row) => row.column_name));
}

async function tableExists(db, tableName) {
  const result = await db.query(
    `
      SELECT EXISTS (
        SELECT 1
          FROM information_schema.tables
         WHERE table_schema = current_schema()
           AND table_name = $1
      ) AS exists
    `,
    [tableName]
  );

  return Boolean(result.rows[0]?.exists);
}

function buildCommitteeSelectFragments(committeeColumns, hasMemberTable) {
  return {
    createdAtExpr: committeeColumns.has("created_at")
      ? "c.created_at"
      : "NULL::timestamptz AS created_at",
    updatedAtExpr: committeeColumns.has("updated_at")
      ? "c.updated_at"
      : "NULL::timestamptz AS updated_at",
    memberCountExpr: hasMemberTable
      ? `(SELECT COUNT(*)::int FROM committee_member cm WHERE cm.committee_id = c.id) AS member_count`
      : `0::int AS member_count`,
  };
}

async function assertUserReference(db, userId, label) {
  if (userId === undefined || userId === null) return;

  const ref = await db.query(
    `
      SELECT id
        FROM app_user
       WHERE id = $1
         AND is_active = TRUE
    `,
    [userId]
  );

  if (!ref.rows[0]) {
    throw Object.assign(new Error(`${label} nicht gefunden`), {
      status: 400,
      publicMessage: `${label} nicht gefunden`,
    });
  }
}

async function assertReferences(db, payload) {
  await assertUserReference(db, payload.default_user_id, "Standard-User");
  await assertUserReference(db, payload.chair_user_id, "Leitung");
  await assertUserReference(db, payload.secretary_user_id, "Schriftführung");
}

async function getCommitteeById(db, id) {
  const committeeColumns = await getTableColumns(db, "committee");
  const hasMemberTable = await tableExists(db, "committee_member");
  const { createdAtExpr, updatedAtExpr, memberCountExpr } = buildCommitteeSelectFragments(
    committeeColumns,
    hasMemberTable
  );

  const result = await db.query(
    `
      SELECT c.id,
             c.name,
             c.committee_type,
             c.default_user_id,
             du.full_name AS default_user_name,
             c.chair_user_id,
             cu.full_name AS chair_user_name,
             c.secretary_user_id,
             su.full_name AS secretary_user_name,
             ${createdAtExpr},
             ${updatedAtExpr},
             ${memberCountExpr}
        FROM committee c
        LEFT JOIN app_user du ON du.id = c.default_user_id
        LEFT JOIN app_user cu ON cu.id = c.chair_user_id
        LEFT JOIN app_user su ON su.id = c.secretary_user_id
       WHERE c.id = $1
    `,
    [id]
  );

  return mapCommitteeRow(result.rows[0]);
}

router.get("/", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const committeeColumns = await getTableColumns(db, "committee");
      const hasMemberTable = await tableExists(db, "committee_member");
      const { createdAtExpr, updatedAtExpr, memberCountExpr } = buildCommitteeSelectFragments(
        committeeColumns,
        hasMemberTable
      );

      const result = await db.query(
        `
          SELECT c.id,
                 c.name,
                 c.committee_type,
                 c.default_user_id,
                 du.full_name AS default_user_name,
                 c.chair_user_id,
                 cu.full_name AS chair_user_name,
                 c.secretary_user_id,
                 su.full_name AS secretary_user_name,
                 ${createdAtExpr},
                 ${updatedAtExpr},
                 ${memberCountExpr}
            FROM committee c
            LEFT JOIN app_user du ON du.id = c.default_user_id
            LEFT JOIN app_user cu ON cu.id = c.chair_user_id
            LEFT JOIN app_user su ON su.id = c.secretary_user_id
           ORDER BY c.name ASC, c.id ASC
        `
      );

      return result.rows.map(mapCommitteeRow);
    });

    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.get("/:id", async (req, res, next) => {
  try {
    const row = await withDbSession(req.user, (db) => getCommitteeById(db, req.params.id));
    if (!row) return res.status(404).json({ error: "Nicht gefunden" });
    res.json(row);
  } catch (err) {
    next(err);
  }
});

router.get("/:id/members", async (req, res, next) => {
  try {
    const rows = await withDbSession(req.user, async (db) => {
      const committee = await getCommitteeById(db, req.params.id);
      if (!committee) {
        throw Object.assign(new Error("Ausschuss nicht gefunden"), {
          status: 404,
          publicMessage: "Ausschuss nicht gefunden",
        });
      }

      const hasMemberTable = await tableExists(db, "committee_member");
      if (!hasMemberTable) {
        return [];
      }

      const result = await db.query(
        `
          SELECT cm.committee_id,
                 u.id AS user_id,
                 u.full_name,
                 u.email,
                 u.role,
                 u.is_active,
                 u.security_classification,
                 cm.created_at
            FROM committee_member cm
            JOIN app_user u ON u.id = cm.user_id
           WHERE cm.committee_id = $1
           ORDER BY u.full_name ASC, u.id ASC
        `,
        [req.params.id]
      );

      return result.rows;
    });

    res.json(rows);
  } catch (err) {
    next(err);
  }
});

router.post(
  "/:id/members",
  requireMfa,
  requirePermission("committees.write", ["admin", "chair", "secretary"]),
  validate(assignMemberSchema),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const committee = await getCommitteeById(db, req.params.id);
        if (!committee) {
          throw Object.assign(new Error("Ausschuss nicht gefunden"), {
            status: 404,
            publicMessage: "Ausschuss nicht gefunden",
          });
        }

        const hasMemberTable = await tableExists(db, "committee_member");
        if (!hasMemberTable) {
          throw Object.assign(new Error("Migration committee_member fehlt"), {
            status: 500,
            publicMessage: "Mitgliedschaftstabelle fehlt. Bitte Migration ausführen.",
          });
        }

        await assertUserReference(db, req.body.user_id, "Benutzer");

        const memberColumns = await getTableColumns(db, "committee_member");
        const insertColumns = ["committee_id", "user_id"];
        const insertValues = [req.params.id, req.body.user_id];
        const placeholders = ["$1", "$2"];

        if (memberColumns.has("created_by")) {
          insertColumns.push("created_by");
          insertValues.push(req.user.sub);
          placeholders.push(`$${insertValues.length}`);
        }

        await db.query(
          `
            INSERT INTO committee_member(
              ${insertColumns.join(", ")}
            )
            VALUES (${placeholders.join(", ")})
            ON CONFLICT (committee_id, user_id) DO NOTHING
          `,
          insertValues
        );

        const result = await db.query(
          `
            SELECT cm.committee_id,
                   u.id AS user_id,
                   u.full_name,
                   u.email,
                   u.role,
                   u.is_active,
                   u.security_classification,
                   cm.created_at
              FROM committee_member cm
              JOIN app_user u ON u.id = cm.user_id
             WHERE cm.committee_id = $1
               AND cm.user_id = $2
          `,
          [req.params.id, req.body.user_id]
        );

        return result.rows[0];
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "COMMITTEE_MEMBER_ASSIGNED",
        entityType: "committee",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: { user_id: req.body.user_id },
      });

      res.status(201).json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.delete(
  "/:id/members/:userId",
  requireMfa,
  requirePermission("committees.write", ["admin", "chair", "secretary"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const committee = await getCommitteeById(db, req.params.id);
        if (!committee) {
          throw Object.assign(new Error("Ausschuss nicht gefunden"), {
            status: 404,
            publicMessage: "Ausschuss nicht gefunden",
          });
        }

        const hasMemberTable = await tableExists(db, "committee_member");
        if (!hasMemberTable) {
          throw Object.assign(new Error("Migration committee_member fehlt"), {
            status: 500,
            publicMessage: "Mitgliedschaftstabelle fehlt. Bitte Migration ausführen.",
          });
        }

        const result = await db.query(
          `
            DELETE FROM committee_member
             WHERE committee_id = $1
               AND user_id = $2
         RETURNING committee_id, user_id
          `,
          [req.params.id, req.params.userId]
        );

        if (!result.rows[0]) {
          throw Object.assign(new Error("Mitgliedschaft nicht gefunden"), {
            status: 404,
            publicMessage: "Mitgliedschaft nicht gefunden",
          });
        }

        return result.rows[0];
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "COMMITTEE_MEMBER_REMOVED",
        entityType: "committee",
        entityId: Number(req.params.id),
        ipAddress: req.ip,
        metadata: { user_id: Number(req.params.userId) },
      });

      res.json({ ok: true, row });
    } catch (err) {
      next(err);
    }
  }
);

router.post(
  "/",
  requireMfa,
  requirePermission("committees.write", ["admin", "chair", "secretary"]),
  validate(createSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        name: req.body.name.trim(),
        committee_type: normalizeNullableText(req.body.committee_type) || "general",
        default_user_id: normalizeNullableNumber(req.body.default_user_id),
        chair_user_id: normalizeNullableNumber(req.body.chair_user_id),
        secretary_user_id: normalizeNullableNumber(req.body.secretary_user_id),
      };

      const row = await withDbSession(req.user, async (db) => {
        await assertReferences(db, payload);

        const committeeColumns = await getTableColumns(db, "committee");
        const insertColumns = [
          "name",
          "committee_type",
          "default_user_id",
          "chair_user_id",
          "secretary_user_id",
        ];
        const insertValues = [
          payload.name,
          payload.committee_type,
          payload.default_user_id,
          payload.chair_user_id,
          payload.secretary_user_id,
        ];
        const placeholders = insertValues.map((_, index) => `$${index + 1}`);

        if (committeeColumns.has("created_by")) {
          insertColumns.push("created_by");
          insertValues.push(req.user.sub);
          placeholders.push(`$${insertValues.length}`);
        }

        if (committeeColumns.has("updated_by")) {
          insertColumns.push("updated_by");
          insertValues.push(req.user.sub);
          placeholders.push(`$${insertValues.length}`);
        }

        const result = await db.query(
          `
            INSERT INTO committee(
              ${insertColumns.join(", ")}
            )
            VALUES (${placeholders.join(", ")})
            RETURNING id
          `,
          insertValues
        );

        return getCommitteeById(db, result.rows[0].id);
      });

      await writeAudit({
        actorId: req.user.sub,
        action: "COMMITTEE_CREATED",
        entityType: "committee",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { payload },
      });

      res.status(201).json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.put(
  "/:id",
  requireMfa,
  requirePermission("committees.write", ["admin", "chair", "secretary"]),
  validate(updateSchema),
  async (req, res, next) => {
    try {
      const payload = {
        ...req.body,
        name: req.body.name !== undefined ? req.body.name.trim() : undefined,
        committee_type:
          req.body.committee_type !== undefined
            ? normalizeNullableText(req.body.committee_type)
            : undefined,
        default_user_id: normalizeNullableNumber(req.body.default_user_id),
        chair_user_id: normalizeNullableNumber(req.body.chair_user_id),
        secretary_user_id: normalizeNullableNumber(req.body.secretary_user_id),
      };

      const row = await withDbSession(req.user, async (db) => {
        const existing = await getCommitteeById(db, req.params.id);
        if (!existing) return null;

        await assertReferences(db, payload);

        const committeeColumns = await getTableColumns(db, "committee");
        const fields = [];
        const values = [];
        let idx = 1;

        const push = (column, value) => {
          fields.push(`${column} = $${idx}`);
          values.push(value);
          idx += 1;
        };

        if (payload.name !== undefined) push("name", payload.name);
        if (payload.committee_type !== undefined) push("committee_type", payload.committee_type || "general");
        if (payload.default_user_id !== undefined) push("default_user_id", payload.default_user_id);
        if (payload.chair_user_id !== undefined) push("chair_user_id", payload.chair_user_id);
        if (payload.secretary_user_id !== undefined) push("secretary_user_id", payload.secretary_user_id);

        if (!fields.length) {
          throw Object.assign(new Error("Keine Felder zum Aktualisieren"), {
            status: 400,
            publicMessage: "Keine Felder zum Aktualisieren",
          });
        }

        if (committeeColumns.has("updated_by")) {
          push("updated_by", req.user.sub);
        }

        values.push(req.params.id);

        await db.query(
          `
            UPDATE committee
               SET ${fields.join(", ")}
             WHERE id = $${idx}
          `,
          values
        );

        return getCommitteeById(db, req.params.id);
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: "COMMITTEE_UPDATED",
        entityType: "committee",
        entityId: row.id,
        ipAddress: req.ip,
        metadata: { payload },
      });

      res.json(row);
    } catch (err) {
      next(err);
    }
  }
);

router.delete(
  "/:id",
  requireMfa,
  requirePermission("committees.write", ["admin", "chair", "secretary"]),
  async (req, res, next) => {
    try {
      const row = await withDbSession(req.user, async (db) => {
        const existing = await getCommitteeById(db, req.params.id);
        if (!existing) return null;

        await db.query(`DELETE FROM committee WHERE id = $1`, [req.params.id]);
        return existing;
      });

      if (!row) return res.status(404).json({ error: "Nicht gefunden" });

      await writeAudit({
        actorId: req.user.sub,
        action: "COMMITTEE_DELETED",
        entityType: "committee",
        entityId: row.id,
        ipAddress: req.ip,
      });

      res.json({ ok: true, row });
    } catch (err) {
      next(err);
    }
  }
);

export default router;
