import { Router } from "express";
import express from "express";
import { z } from "zod";
import { requireAuth } from "../../middleware/auth.js";
import type { SessionUser } from "../../middleware/auth.js";
import { validateBody } from "../../middleware/validate.js";
import { query, one } from "../../db/client.js";
import { badRequest, notFound } from "../../lib/errors.js";
import {
  clearPhoto,
  getPhoto,
  setPhoto,
  updateEducation,
} from "../members/service.js";

/**
 * Self-service profile: a signed-in user edits ONLY the member file linked to their
 * own account (users.member_id). Server-enforced: no member id is ever taken from
 * the request. Permitted fields only — name / official number / formal status stay
 * staff-managed. Restricted data (contact details) still goes through the masked
 * contact flows with their own permissions.
 */
export const myProfileRouter = Router();
myProfileRouter.use(requireAuth);

async function requireLinkedMember(req: { user: SessionUser }): Promise<{ id: number; public_uuid: string; full_name: string }> {
  const row = await one<{ id: number; public_uuid: string; full_name: string }>(
    `SELECT m.id, m.public_uuid::text AS public_uuid, m.full_name
       FROM users u JOIN members m ON m.id = u.member_id
      WHERE u.id = $1 AND m.deleted_at IS NULL`,
    [req.user!.id],
  );
  if (!row) throw notFound("لا يوجد ملف عضو مرتبط بحسابك — راجع مسؤول النظام (no_member_file)");
  return row;
}

myProfileRouter.get("/", async (req, res, next) => {
  try {
    const me = await requireLinkedMember(req as { user: SessionUser });
    const member = await one(
      `SELECT m.public_uuid::text AS public_uuid, m.full_name, m.membership_no, m.university, m.faculty,
              m.qualification, m.graduation_year, g.name_ar AS governorate_name,
              s.name_ar AS formal_status_ar, (m.photo IS NOT NULL) AS has_photo,
              mc.phone_primary IS NOT NULL AS has_phone, mc.email IS NOT NULL AS has_email
         FROM members m
         LEFT JOIN governorates g ON g.id = m.governorate_id
         LEFT JOIN statuses s ON s.id = m.formal_membership_status_id
         LEFT JOIN member_contacts mc ON mc.member_id = m.id
        WHERE m.id = $1`,
      [me.id],
    );
    const affiliations = (await query(
      `SELECT id, kind, org_name, member_role, started_on::text, ended_on::text
         FROM member_affiliations WHERE member_id = $1 AND deleted_at IS NULL ORDER BY id DESC`,
      [me.id],
    )).rows;
    const internal = (await query(
      `SELECT p.id, p.title_ar, u.name_ar AS unit_name, p.started_on::text, p.ended_on::text
         FROM org_positions p JOIN org_units u ON u.id = p.org_unit_id
        WHERE p.member_id = $1 AND p.deleted_at IS NULL
        ORDER BY p.ended_on NULLS FIRST, p.id DESC`,
      [me.id],
    )).rows;
    const external = (await query(
      `SELECT id, kind, org_name, title, started_on::text, ended_on::text
         FROM member_external_positions WHERE member_id = $1 AND deleted_at IS NULL ORDER BY id DESC`,
      [me.id],
    )).rows;

    // completion: recommended fields only — percentages, never private values, to others
    const checks: boolean[] = [
      Boolean((member as { full_name: string }).full_name),
      Boolean((member as { governorate_name: string | null }).governorate_name),
      Boolean((member as { membership_no: string | null }).membership_no),
      Boolean((member as { has_phone: boolean }).has_phone),
      Boolean((member as { has_photo: boolean }).has_photo),
      Boolean((member as { university: string | null }).university),
      affiliations.length > 0,
    ];
    const filled = checks.filter(Boolean).length;
    const completion = { percent: Math.round((filled / checks.length) * 100), missing: [] as string[] };
    const labels = ["full_name", "governorate", "membership_no", "phone", "photo", "education", "affiliations"];
    checks.forEach((ok, i) => { if (!ok) completion.missing.push(labels[i]!); });

    res.json({ member, affiliations, externalPositions: external, internalPositions: internal, completion });
  } catch (error) {
    next(error);
  }
});

myProfileRouter.put(
  "/education",
  validateBody(
    z.object({
      university: z.string().max(200).nullable().optional(),
      faculty: z.string().max(200).nullable().optional(),
      qualification: z.string().max(200).nullable().optional(),
      graduationYear: z.coerce.number().int().min(1950).max(2100).nullable().optional(),
    }),
  ),
  async (req, res, next) => {
    try {
      const me = await requireLinkedMember(req as { user: SessionUser });
      await updateEducation(req, me.id, req.body);
      res.json({ ok: true });
    } catch (error) {
      next(error);
    }
  },
);

/** Official photo upload: raw image body, strict type+size validation, stored in the DB. */
const ALLOWED = new Map([
  ["image/jpeg", "jpeg"],
  ["image/png", "png"],
  ["image/webp", "webp"],
]);

myProfileRouter.post(
  "/photo",
  express.raw({ type: ["image/jpeg", "image/png", "image/webp"], limit: "2mb" }),
  async (req, res, next) => {
    try {
      const mime = (req.headers["content-type"] ?? "").split(";")[0]!.trim();
      if (!ALLOWED.has(mime)) throw badRequest("صيغة الصورة غير مدعومة (JPEG/PNG/WebP فقط)", "unsupported_media_type");
      const bytes = req.body as Buffer;
      if (!Buffer.isBuffer(bytes) || bytes.length < 50) throw badRequest("ملف الصورة فارغ أو تالف", "invalid_photo");
      const me = await requireLinkedMember(req as { user: SessionUser });
      await setPhoto(req, me.id, bytes, mime);
      res.json({ ok: true });
    } catch (error) {
      next(error);
    }
  },
);

myProfileRouter.delete("/photo", async (req, res, next) => {
  try {
    const me = await requireLinkedMember(req as { user: SessionUser });
    await clearPhoto(req, me.id);
    res.json({ ok: true });
  } catch (error) {
    next(error);
  }
});

/** Affiliations (MEMBERSHIP in external bodies) — multiple entries. */
myProfileRouter.post(
  "/affiliations",
  validateBody(
    z.object({
      kind: z.enum(["syndicate", "club", "ngo", "university", "other"]),
      orgName: z.string().min(2).max(200),
      memberRole: z.string().max(150).nullable().optional(),
      startedOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
      endedOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
    }),
  ),
  async (req, res, next) => {
    try {
      const me = await requireLinkedMember(req as { user: SessionUser });
      const b = req.body as { kind: string; orgName: string; memberRole?: string | null; startedOn?: string | null; endedOn?: string | null };
      await query(
        `INSERT INTO member_affiliations (member_id, kind, org_name, member_role, started_on, ended_on, created_by)
         VALUES ($1, $2, $3, $4, $5, $6, $7)`,
        [me.id, b.kind, b.orgName.trim(), b.memberRole?.trim() || null, b.startedOn || null, b.endedOn || null, req.user!.id],
      );
      res.status(201).json({ ok: true });
    } catch (error) {
      next(error);
    }
  },
);

myProfileRouter.delete("/affiliations/:id", async (req, res, next) => {
  try {
    const me = await requireLinkedMember(req as { user: SessionUser });
    const r = await query(
      `UPDATE member_affiliations SET deleted_at = now() WHERE id = $1 AND member_id = $2 AND deleted_at IS NULL`,
      [(req.params.id as unknown as number), me.id],
    );
    if (!r.rowCount) throw notFound("السجل غير موجود");
    res.json({ ok: true });
  } catch (error) {
    next(error);
  }
});

/** External POSITIONS (holding an office) — distinct from affiliations by design. */
myProfileRouter.post(
  "/external-positions",
  validateBody(
    z.object({
      orgName: z.string().min(2).max(200),
      title: z.string().min(2).max(150),
      kind: z.enum(["board", "council", "other"]),
      startedOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
      endedOn: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).nullable().optional(),
    }),
  ),
  async (req, res, next) => {
    try {
      const me = await requireLinkedMember(req as { user: SessionUser });
      const b = req.body as { orgName: string; title: string; kind: string; startedOn?: string | null; endedOn?: string | null };
      await query(
        `INSERT INTO member_external_positions (member_id, org_name, title, kind, started_on, ended_on, created_by)
         VALUES ($1, $2, $3, $4, $5, $6, $7)`,
        [me.id, b.orgName.trim(), b.title.trim(), b.kind, b.startedOn || null, b.endedOn || null, req.user!.id],
      );
      res.status(201).json({ ok: true });
    } catch (error) {
      next(error);
    }
  },
);

myProfileRouter.delete("/external-positions/:id", async (req, res, next) => {
  try {
    const me = await requireLinkedMember(req as { user: SessionUser });
    const r = await query(
      `UPDATE member_external_positions SET deleted_at = now() WHERE id = $1 AND member_id = $2 AND deleted_at IS NULL`,
      [(req.params.id as unknown as number), me.id],
    );
    if (!r.rowCount) throw notFound("السجل غير موجود");
    res.json({ ok: true });
  } catch (error) {
    next(error);
  }
});
