import type { Request } from "express";
import { one, query } from "../../db/client.js";
import { audit } from "../../lib/audit.js";
import { badRequest, forbidden, notFound } from "../../lib/errors.js";

/**
 * KPI module (DEC-04 approved authority, owner order 2026-10-29).
 * Approval requires kpis.approve permission AND an ACTIVE assignment of one of the
 * three authority roles: youth_exec_office | party_exec_office | secretary_general.
 * No composite score, separate indicators only (approved spec v0.1 §1).
 */

const AUTHORITY_ROLES = ["youth_exec_office", "party_exec_office", "secretary_general"];

/** Server-side authority check: permission + active authority-role membership. */
export async function requireKpiAuthority(req: Request): Promise<void> {
  if (!req.user!.permissions.has("kpis.approve")) {
    throw forbidden("اعتماد المؤشرات يتطلب صلاحية kpis.approve");
  }
  const r = await one<{ n: number }>(
    `SELECT COUNT(*)::int AS n
       FROM user_roles ur
       JOIN roles ro ON ro.id = ur.role_id AND ro.is_enabled
      WHERE ur.user_id = $1 AND ur.revoked_at IS NULL
        AND (ur.valid_from IS NULL OR ur.valid_from <= now())
        AND (ur.valid_until IS NULL OR ur.valid_until > now())
        AND ro.code = ANY($2)`,
    [req.user!.id, AUTHORITY_ROLES],
  );
  if (!r || r.n === 0) {
    throw forbidden("الاعتماد مقتصر على أعضاء المكتبين التنفيذيين أو الأمين العام وفق إسناد سارٍ");
  }
}

export interface KpiDefinitionRow {
  id: number;
  code: string;
  name_ar: string;
  name_en: string | null;
  description: string | null;
  unit: string;
  direction: string;
  status: string;
  approved_at: string | null;
  targets: { period: string; scope_type: string; org_unit_id: number | null; org_unit_name: string | null; target_value: string }[];
}

export async function listDefinitions(): Promise<KpiDefinitionRow[]> {
  const defs = (await query<KpiDefinitionRow & { approved_by: number | null }>(
    `SELECT id, code, name_ar, name_en, description, unit, direction, status,
            approved_at::text, approved_by, created_at::text
       FROM kpi_definitions ORDER BY id`,
  )).rows;
  const targets = (await query(
    `SELECT t.definition_id, t.period, t.scope_type, t.org_unit_id, u.name_ar AS org_unit_name, t.target_value::text
       FROM kpi_targets t LEFT JOIN org_units u ON u.id = t.org_unit_id
      ORDER BY t.period, t.id`,
  )).rows as { definition_id: number; period: string; scope_type: string; org_unit_id: number | null; org_unit_name: string | null; target_value: string }[];
  return defs.map((d) => ({
    ...d,
    targets: targets.filter((t) => t.definition_id === d.id).map(({ definition_id: _d, ...rest }) => rest),
  }));
}

export async function getDefinition(id: number): Promise<KpiDefinitionRow | null> {
  const all = await listDefinitions();
  return all.find((d) => d.id === id) ?? null;
}

export async function createDefinition(
  req: Request,
  input: { code: string; nameAr: string; nameEn?: string | null; description?: string | null; unit: string; direction: string },
): Promise<KpiDefinitionRow> {
  if (!/^[a-z0-9_]{3,50}$/.test(input.code)) {
    throw badRequest("كود المؤشر: حروف لاتينية صغيرة/أرقام/شرطة سفلية (3-50)", "invalid_code");
  }
  const dup = await one(`SELECT id FROM kpi_definitions WHERE code = $1`, [input.code]);
  if (dup) throw badRequest("كود المؤشر مستخدم", "code_taken");
  const row = await one<{ id: number }>(
    `INSERT INTO kpi_definitions (code, name_ar, name_en, description, unit, direction, created_by)
     VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
    [input.code, input.nameAr.trim(), input.nameEn?.trim() || null, input.description?.trim() || null, input.unit, input.direction, req.user!.id],
  );
  await audit({
    actorUserId: req.user!.id,
    action: "kpis.definition_created",
    entityType: "kpi_definition",
    entityId: row!.id,
    changedFields: { code: input.code, nameAr: input.nameAr.trim(), unit: input.unit, direction: input.direction },
    ip: req.ip ?? null,
    userAgent: req.headers["user-agent"] ?? null,
  });
  const def = await getDefinition(row!.id);
  if (!def) throw new Error("kpi vanished");
  return def;
}

/** Authority approval: draft → active (DEC-04 trio enforced in requireKpiAuthority). */
export async function approveDefinition(req: Request, id: number): Promise<void> {
  await requireKpiAuthority(req);
  const def = await one<{ id: number; status: string; code: string }>(`SELECT id, status, code FROM kpi_definitions WHERE id = $1`, [id]);
  if (!def) throw notFound("المؤشر غير موجود");
  if (def.status !== "draft") throw badRequest("المؤشر معتمد أو مؤرشف بالفعل", "invalid_status");
  await query(`UPDATE kpi_definitions SET status = 'active', approved_by = $2, approved_at = now(), updated_at = now(), updated_by = $2 WHERE id = $1`, [id, req.user!.id]);
  await audit({
    actorUserId: req.user!.id,
    action: "kpis.definition_approved",
    entityType: "kpi_definition",
    entityId: id,
    changedFields: { from: "draft", to: "active", code: def.code },
    ip: req.ip ?? null,
    userAgent: req.headers["user-agent"] ?? null,
  });
}

export async function setTarget(
  req: Request,
  input: { definitionId: number; period: string; scopeType: "national" | "secretariat"; orgUnitId: number | null; targetValue: number },
): Promise<void> {
  await requireKpiAuthority(req);
  if (input.scopeType === "secretariat" && !input.orgUnitId) {
    throw badRequest("هدف الأمانة يتطلب تحديد الوحدة", "org_unit_required");
  }
  const def = await one<{ status: string }>(`SELECT status FROM kpi_definitions WHERE id = $1`, [input.definitionId]);
  if (!def) throw notFound("المؤشر غير موجود");
  if (def.status !== "active") throw badRequest("الأهداف تُعتمد على مؤشر نشط فقط", "definition_not_active");
  await query(
    `INSERT INTO kpi_targets (definition_id, period, scope_type, org_unit_id, target_value, approved_by, approved_at, created_by)
     VALUES ($1, $2, $3, $4, $5, $6, now(), $6)
     ON CONFLICT (definition_id, period, scope_type, org_unit_id)
     DO UPDATE SET target_value = EXCLUDED.target_value, approved_by = EXCLUDED.approved_by, approved_at = now()`,
    [input.definitionId, input.period, input.scopeType, input.orgUnitId, input.targetValue, req.user!.id],
  );
  await audit({
    actorUserId: req.user!.id,
    action: "kpis.target_set",
    entityType: "kpi_definition",
    entityId: input.definitionId,
    changedFields: { period: input.period, scopeType: input.scopeType, orgUnitId: input.orgUnitId, targetValue: input.targetValue },
    ip: req.ip ?? null,
    userAgent: req.headers["user-agent"] ?? null,
  });
}

export async function submitMeasurement(
  req: Request,
  input: { definitionId: number; period: string; scopeType: "national" | "secretariat"; orgUnitId: number | null; value: number; evidenceRef?: string | null },
): Promise<void> {
  if (!req.user!.permissions.has("kpis.manage")) throw forbidden("إدخال القياسات يتطلب kpis.manage");
  const def = await one<{ status: string }>(`SELECT status FROM kpi_definitions WHERE id = $1`, [input.definitionId]);
  if (!def || def.status !== "active") throw badRequest("القياس على مؤشر نشط فقط", "definition_not_active");
  const ins = await one<{ id: number }>(
    `INSERT INTO kpi_measurements (definition_id, period, scope_type, org_unit_id, value, evidence_ref, submitted_by)
     VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
    [input.definitionId, input.period, input.scopeType, input.orgUnitId, input.value, input.evidenceRef?.trim() || null, req.user!.id],
  );
  await audit({
    actorUserId: req.user!.id,
    action: "kpis.measurement_submitted",
    entityType: "kpi_measurement",
    entityId: ins!.id,
    changedFields: { definitionId: input.definitionId, period: input.period, value: input.value, scopeType: input.scopeType, orgUnitId: input.orgUnitId },
    ip: req.ip ?? null,
    userAgent: req.headers["user-agent"] ?? null,
  });
}

export async function reviewMeasurement(
  req: Request,
  id: number,
  decision: "approved" | "rejected",
  reviewNote: string | null,
): Promise<void> {
  await requireKpiAuthority(req);
  const m = await one<{ id: number; status: string; definition_id: number }>(`SELECT id, status, definition_id FROM kpi_measurements WHERE id = $1`, [id]);
  if (!m) throw notFound("القياس غير موجود");
  if (m.status !== "submitted") throw badRequest("القياس مراجَع بالفعل", "already_reviewed");
  await query(`UPDATE kpi_measurements SET status = $2, review_note = $3, reviewed_by = $4, reviewed_at = now() WHERE id = $1`, [id, decision, reviewNote?.trim() || null, req.user!.id]);
  await audit({
    actorUserId: req.user!.id,
    action: decision === "approved" ? "kpis.measurement_approved" : "kpis.measurement_rejected",
    entityType: "kpi_measurement",
    entityId: id,
    changedFields: { from: "submitted", to: decision },
    context: { reviewNote: reviewNote?.trim() || null },
    ip: req.ip ?? null,
    userAgent: req.headers["user-agent"] ?? null,
  });
}

export async function listMeasurements(): Promise<unknown[]> {
  return (
    await query(
      `SELECT m.id, d.code AS kpi_code, d.name_ar AS kpi_name, m.period, m.scope_type, u.name_ar AS org_unit_name,
              m.value::text, m.status, m.review_note, m.submitted_at::text, mem.full_name AS submitted_by_name
         FROM kpi_measurements m
         JOIN kpi_definitions d ON d.id = m.definition_id
         LEFT JOIN org_units u ON u.id = m.org_unit_id
         LEFT JOIN users us ON us.id = m.submitted_by
         LEFT JOIN members mem ON mem.id = us.member_id
        ORDER BY m.submitted_at DESC, m.id DESC`,
    )
  ).rows;
}
