"use client";

import type {
  RgcDecisionRow,
  RgcDecisionStatus,
} from "../data/rgc-decision-data";

const API_BASE_URL =
  process.env.NEXT_PUBLIC_API_URL?.replace(/\/$/, "") ??
  "http://localhost:3001/api/v1";

export type RgcDecisionLookupOption = {
  id: number;
  label: string;
};

export type RgcDecisionLookups = {
  plenaries: RgcDecisionLookupOption[];
  ministries: RgcDecisionLookupOption[];
  categories: RgcDecisionLookupOption[];
  indicators?: RgcDecisionLookupOption[];
  issues?: Array<{ id: number; title: string; description?: string | null }>;
};

export type CreateCdcRgcDecisionPayload = {
  plenaryId: number;
  stakeholderId: number;
  categoryId: number;
  indicatorId?: number;
  meetingDate: string;
  status: string;
  focalPerson: string;
  decision: string;
  verificationSource?: string;
  verificationLink?: string;
  issueIds?: number[]; // 🔥 បន្ថែម issueIds សម្រាប់ Linked Issues
  saveAsDraft?: boolean;
};

type UnknownObject = Record<string, unknown>;

function isObject(
  value: unknown,
): value is UnknownObject {
  return (
    value !== null &&
    typeof value === "object" &&
    !Array.isArray(value)
  );
}

function toPositiveInteger(
  value: unknown,
): number | null {
  const parsed = Number(value);

  if (
    !Number.isInteger(parsed) ||
    parsed < 1
  ) {
    return null;
  }

  return parsed;
}

function normalizeRoleName(value: unknown): string {
  return String(value ?? "")
    .trim()
    .toLowerCase()
    .replace(/[\s-]+/g, "_");
}

function readStoredObject(
  rawValue: string | null,
): UnknownObject | null {
  if (!rawValue) {
    return null;
  }

  try {
    const parsed: unknown = JSON.parse(rawValue);

    return isObject(parsed) ? parsed : null;
  } catch {
    return null;
  }
}

function readUserId(
  value: UnknownObject,
): number | null {
  const nestedData = isObject(value.data)
    ? value.data
    : null;

  const nestedUser = isObject(value.user)
    ? value.user
    : null;

  return (
    toPositiveInteger(value.id) ??
    toPositiveInteger(value.userId) ??
    toPositiveInteger(nestedData?.id) ??
    toPositiveInteger(nestedData?.userId) ??
    toPositiveInteger(nestedUser?.id) ??
    toPositiveInteger(nestedUser?.userId)
  );
}

function readRoleNames(
  value: UnknownObject,
): string[] {
  const nestedData = isObject(value.data)
    ? value.data
    : null;

  const nestedUser = isObject(value.user)
    ? value.user
    : null;

  const roleSources: unknown[] = [
    value.role,
    value.roleName,
    value.roles,
    nestedData?.role,
    nestedData?.roleName,
    nestedData?.roles,
    nestedUser?.role,
    nestedUser?.roleName,
    nestedUser?.roles,
  ];

  const result: string[] = [];

  for (const source of roleSources) {
    if (typeof source === "string") {
      result.push(normalizeRoleName(source));
      continue;
    }

    if (Array.isArray(source)) {
      for (const item of source) {
        if (typeof item === "string") {
          result.push(normalizeRoleName(item));
        } else if (isObject(item)) {
          result.push(
            normalizeRoleName(
              item.name ??
                item.roleName ??
                item.code,
            ),
          );
        }
      }
      continue;
    }

    if (isObject(source)) {
      result.push(
        normalizeRoleName(
          source.name ??
            source.roleName ??
            source.code,
        ),
      );
    }
  }

  return result.filter(Boolean);
}

function getCurrentUserId(): number {
  if (typeof window === "undefined") {
    return 4;
  }

  const storageKeys = [
    "currentUser",
    "authUser",
    "user",
    "profile",
    "auth",
    "loginResponse",
  ];

  for (const key of storageKeys) {
    const stored =
      readStoredObject(
        localStorage.getItem(key),
      ) ??
      readStoredObject(
        sessionStorage.getItem(key),
      );

    if (!stored) {
      continue;
    }

    const roles = readRoleNames(stored);

    const isCdcAccount = roles.some(
      (role) =>
        role === "cdc" ||
        role === "cdc_g_psf" ||
        role === "cdc_gpsf",
    );

    if (!isCdcAccount) {
      continue;
    }

    const userId = readUserId(stored);

    if (userId) {
      return userId;
    }
  }

  return 4;
}

function buildHeaders(
  includeJson = false,
): HeadersInit {
  const userId = getCurrentUserId();

  return {
    Accept: "application/json",
    "x-user-id": String(userId),

    ...(includeJson
      ? {
          "Content-Type": "application/json",
        }
      : {}),
  };
}

function readString(
  object: UnknownObject | null,
  keys: string[],
): string {
  if (!object) {
    return "";
  }

  for (const key of keys) {
    const value = object[key];

    if (
      typeof value === "string" &&
      value.trim()
    ) {
      return value.trim();
    }

    if (
      typeof value === "number" &&
      Number.isFinite(value)
    ) {
      return String(value);
    }
  }

  return "";
}

function readObject(
  object: UnknownObject | null,
  keys: string[],
): UnknownObject | null {
  if (!object) {
    return null;
  }

  for (const key of keys) {
    const value = object[key];

    if (isObject(value)) {
      return value;
    }
  }

  return null;
}

function extractArray(
  payload: unknown,
): UnknownObject[] {
  if (Array.isArray(payload)) {
    return payload.filter(isObject);
  }

  if (!isObject(payload)) {
    return [];
  }

  const candidates = [
    payload.items,
    payload.results,
    payload.rows,
    payload.records,
    payload.list,
    payload.data,
  ];

  for (const candidate of candidates) {
    if (Array.isArray(candidate)) {
      return candidate.filter(isObject);
    }
  }

  if (isObject(payload.data)) {
    const nestedCandidates = [
      payload.data.items,
      payload.data.results,
      payload.data.rows,
      payload.data.records,
      payload.data.list,
      payload.data.data,
    ];

    for (const candidate of nestedCandidates) {
      if (Array.isArray(candidate)) {
        return candidate.filter(isObject);
      }
    }
  }

  return [];
}

function collectErrorMessages(
  value: unknown,
): string[] {
  if (typeof value === "string") {
    const text = value.trim();

    return text ? [text] : [];
  }

  if (Array.isArray(value)) {
    return value.flatMap((item) =>
      collectErrorMessages(item),
    );
  }

  if (!isObject(value)) {
    return [];
  }

  return [
    ...collectErrorMessages(value.message),
    ...collectErrorMessages(value.messages),
    ...collectErrorMessages(value.errors),
    ...collectErrorMessages(value.details),
    ...collectErrorMessages(
      value.validationErrors,
    ),
  ];
}

function readApiErrorMessage(
  payload: unknown,
  fallback: string,
): string {
  const messages = collectErrorMessages(
    payload,
  )
    .map((message) => message.trim())
    .filter(Boolean)
    .filter(
      (message, index, values) =>
        values.indexOf(message) === index,
    );

  if (messages.length > 0) {
    return messages.join(", ");
  }

  if (isObject(payload)) {
    const error = payload.error;

    if (
      typeof error === "string" &&
      error.trim()
    ) {
      return error.trim();
    }
  }

  return fallback;
}

function normalizeDate(
  value: unknown,
): string {
  if (typeof value !== "string") {
    return "";
  }

  const text = value.trim();

  const isoMatch =
    /^(\d{4})-(\d{2})-(\d{2})/.exec(
      text,
    );

  if (isoMatch) {
    return `${isoMatch[1]}-${isoMatch[2]}-${isoMatch[3]}`;
  }

  return text;
}

function normalizeOptionalUrl(
  value: string | undefined,
): string | undefined {
  const text = String(value ?? "").trim();

  if (!text) {
    return undefined;
  }

  if (
    text.startsWith("http://") ||
    text.startsWith("https://")
  ) {
    return text;
  }

  return `https://${text}`;
}

function normalizeStatus(
  value: unknown,
): RgcDecisionStatus {
  const normalized = String(
    value ?? "",
  )
    .trim()
    .toUpperCase()
    .replaceAll("_", " ")
    .replaceAll("-", " ");

  if (normalized === "SOLVED") {
    return "Solved";
  }

  if (normalized === "IN PROGRESS") {
    return "In Progress";
  }

  return "Not Addressed";
}

function normalizeCreateStatus(
  value: string,
): string {
  const normalized = value
    .trim()
    .toUpperCase()
    .replaceAll("-", "_")
    .replaceAll(" ", "_");

  if (normalized === "SOLVED") {
    return "SOLVED";
  }

  if (normalized === "IN_PROGRESS") {
    return "IN_PROGRESS";
  }

  return "NOT_ADDRESSED";
}

function mapItem(
  item: UnknownObject,
): RgcDecisionRow | null {
  const id =
    toPositiveInteger(item.id) ??
    toPositiveInteger(item.decisionId) ??
    toPositiveInteger(item.rgcDecisionId);

  if (!id) {
    return null;
  }

  const stakeholder = readObject(item, [
    "stakeholder",
    "ministry",
    "primaryAgency",
    "agency",
  ]);

  const categoryObject = readObject(item, [
    "categoryInfo",
    "category",
    "measureCategory",
  ]);

  const indicatorObject = readObject(item, [
    "indicator",
  ]);

  const plenaryObject = readObject(item, [
    "plenary",
  ]);

  const ministry =
    readString(item, [
      "ministryName",
      "stakeholderName",
      "primaryAgencyName",
      "agencyName",
    ]) ||
    readString(stakeholder, [
      "name",
      "title",
      "label",
      "shortName",
    ]);

  const ministryLogo =
    readString(item, [
      "ministryLogo",
      "stakeholderLogo",
      "agencyLogo",
      "logo",
    ]) ||
    readString(stakeholder, [
      "logo",
      "logoUrl",
      "image",
      "imageUrl",
    ]);

  const category =
    readString(item, [
      "categoryName",
      "measureCategory",
    ]) ||
    readString(categoryObject, [
      "name",
      "title",
      "label",
    ]) ||
    (typeof item.category === "string"
      ? item.category.trim()
      : "");

  const meetingDate = normalizeDate(
    readString(item, [
      "meetingDate",
      "effectiveDate",
      "decisionDate",
      "date",
    ]) ||
      readString(plenaryObject, [
        "meetingDate",
        "effectiveDate",
        "date",
      ]),
  );

  const verificationLink =
    readString(item, [
      "verificationLink",
      "verificationDownloadUrl",
      "downloadUrl",
      "linkToVerificationSource",
      "sourceLink",
    ]) || null;

  // Map linked issues without using `any`.
  const issues = Array.isArray(item.issues)
    ? item.issues
        .filter(isObject)
        .map((issue, index) => {
          const issueId =
            toPositiveInteger(issue.id) ?? 0;

          return {
            id: issueId,
            title:
              readString(issue, [
                "title",
                "name",
                "label",
              ]) ||
              `Issue ${issueId || index + 1}`,
            description:
              readString(issue, [
                "description",
              ]) || null,
          };
        })
        .filter((issue) => issue.id > 0)
    : [];

  return {
    id,

    plenaryId:
      toPositiveInteger(item.plenaryId) ??
      toPositiveInteger(
        plenaryObject?.id,
      ),

    plenary:
      readString(item, [
        "plenaryName",
        "plenaryTitle",
      ]) ||
      readString(plenaryObject, [
        "name",
        "title",
        "label",
      ]),

    ministryId:
      toPositiveInteger(
        item.stakeholderId,
      ) ??
      toPositiveInteger(item.ministryId) ??
      toPositiveInteger(
        item.primaryAgencyId,
      ) ??
      toPositiveInteger(
        stakeholder?.id,
      ),

    ministry,
    ministryLogo:
      ministryLogo || null,

    decision: readString(item, [
      "decision",
      "rgcDecision",
      "decisionText",
      "description",
      "content",
      "title",
    ]),

    meetingDate,

    categoryId:
      toPositiveInteger(item.categoryId) ??
      toPositiveInteger(
        categoryObject?.id,
      ),

    category,

    status: normalizeStatus(
      item.statusCode ??
        item.status ??
        item.decisionStatus,
    ),

    indicatorId:
      toPositiveInteger(
        item.indicatorId,
      ) ??
      toPositiveInteger(
        indicatorObject?.id,
      ),

    indicator:
      readString(item, [
        "indicatorName",
      ]) ||
      readString(indicatorObject, [
        "name",
        "title",
        "label",
      ]),

    focalPerson: readString(item, [
      "focalPerson",
      "focalPersonName",
      "focalPersonHe",
      "focalPersonHE",
    ]),

    sourceOfVerification:
      readString(item, [
        "verificationSource",
        "sourceOfVerification",
        "verification",
      ]),

    verificationLink,
    verificationDownloadUrl:
      verificationLink,

    workingGroup:
      readString(item, [
        "workingGroupName",
      ]) ||
      readString(
        readObject(item, [
          "workingGroup",
        ]),
        [
          "name",
          "title",
          "label",
        ],
      ),

    primaryAgency: ministry,
    measureCategory: category,

    effectiveDate:
      normalizeDate(
        item.effectiveDate,
      ) || meetingDate,

    issues, // 🔥 បញ្ចូល issues
  };
}

async function requestJson(
  url: string,
): Promise<unknown> {
  const response = await fetch(url, {
    method: "GET",
    credentials: "include",
    cache: "no-store",
    headers: buildHeaders(),
  });

  const payload = await response
    .json()
    .catch(() => null);

  if (!response.ok) {
    throw new Error(
      readApiErrorMessage(
        payload,
        `Request failed (${response.status}).`,
      ),
    );
  }

  return payload;
}

async function fetchLookup(
  endpoint: string,
): Promise<RgcDecisionLookupOption[]> {
  const payload = await requestJson(
    `${API_BASE_URL}${endpoint}`,
  );

  const options = new Map<
    number,
    RgcDecisionLookupOption
  >();

  extractArray(payload).forEach((item) => {
    const id =
      toPositiveInteger(item.id) ??
      toPositiveInteger(item.value);

    const label = readString(item, [
      "name",
      "label",
      "title",
      "shortName",
    ]);

    if (id && label) {
      options.set(id, {
        id,
        label,
      });
    }
  });

  return Array.from(
    options.values(),
  ).sort((first, second) =>
    first.label.localeCompare(
      second.label,
    ),
  );
}

export async function getCdcRgcDecisions(): Promise<
  RgcDecisionRow[]
> {
  const payload = await requestJson(
    `${API_BASE_URL}/rgc-decisions/cdc-gpsf?limit=100`,
  );

  return extractArray(payload)
    .map(mapItem)
    .filter(
      (
        item,
      ): item is RgcDecisionRow =>
        item !== null,
    );
}

export async function getCdcRgcDecisionLookups(): Promise<RgcDecisionLookups> {
  const [
    plenaries,
    ministries,
    categories,
    issuesResponse,
  ] = await Promise.all([
    fetchLookup(
      "/rgc-decisions/lookups/plenaries",
    ),
    fetchLookup(
      "/rgc-decisions/lookups/ministries",
    ),
    fetchLookup(
      "/rgc-decisions/lookups/categories",
    ),
    requestJson(`${API_BASE_URL}/rgc-decisions/lookups/issues`).catch(() => null),
  ]);

  const issues = extractArray(issuesResponse).map((item) => ({
    id: Number(item.id) || 0,
    title: readString(item, ["title", "name"]) || `Issue ${item.id}`,
    description: readString(item, ["description"]) || null,
  })).filter((i) => i.id > 0);

  return {
    plenaries:
      Array.isArray(plenaries)
        ? plenaries
        : [],

    ministries:
      Array.isArray(ministries)
        ? ministries
        : [],

    categories:
      Array.isArray(categories)
        ? categories
        : [],

    issues,
  };
}

export async function createCdcRgcDecision(
  payload: CreateCdcRgcDecisionPayload,
): Promise<unknown> {
  const userId = getCurrentUserId();

  if (
    !Number.isInteger(payload.plenaryId) ||
    payload.plenaryId < 1
  ) {
    throw new Error(
      "Please select a valid Plenary.",
    );
  }

  if (
    !Number.isInteger(
      payload.stakeholderId,
    ) ||
    payload.stakeholderId < 1
  ) {
    throw new Error(
      "Please select a valid Ministry.",
    );
  }

  if (
    !Number.isInteger(
      payload.categoryId,
    ) ||
    payload.categoryId < 1
  ) {
    throw new Error(
      "Please select a valid Category.",
    );
  }

  const requestBody = {
    plenaryId: payload.plenaryId,
    stakeholderId:
      payload.stakeholderId,
    categoryId: payload.categoryId,

    ...(payload.indicatorId
      ? {
          indicatorId:
            payload.indicatorId,
        }
      : {}),

    meetingDate: normalizeDate(
      payload.meetingDate,
    ),

    status: normalizeCreateStatus(
      payload.status,
    ),

    focalPerson:
      payload.focalPerson.trim(),

    decision:
      payload.decision.trim(),

    verificationSource:
      payload.verificationSource?.trim() ||
      undefined,

    verificationLink:
      normalizeOptionalUrl(
        payload.verificationLink,
      ),

    issueIds: payload.issueIds ?? [], // 🔥 បញ្ជូនបញ្ជី issueIds ទៅ Back-end
    saveAsDraft:
      payload.saveAsDraft ?? false,
  };

  const response = await fetch(
    `${API_BASE_URL}/rgc-decisions/cdc-gpsf`,
    {
      method: "POST",
      credentials: "include",
      cache: "no-store",

      headers: {
        Accept: "application/json",
        "Content-Type":
          "application/json",
        "x-user-id": String(userId),
      },

      body: JSON.stringify(requestBody),
    },
  );

  const responseText =
    await response.text();

  let responsePayload: unknown = null;

  if (responseText.trim()) {
    try {
      responsePayload =
        JSON.parse(responseText);
    } catch {
      responsePayload =
        responseText.trim();
    }
  }

  if (!response.ok) {
    throw new Error(
      readApiErrorMessage(
        responsePayload,
        `Unable to create RGC Decision (${response.status}).`,
      ),
    );
  }

  return responsePayload;
}