import { redirectToLoginAfterSessionExpired } from "@/features/auth/service/auth-service";
import type { UploadedFileMetadata } from "@/lib/document-file";
import type { IssueStatus } from "@/features/pswg/working-group-issues/wg-issues-data";

import type {
  CdcIssueMatrixRow,
  CdcIssueMatrixStatus,
  CdcIssueMatrixSummary,
} from "../components/cdc-issue-matrix-data";

type ApiResponse<T> = {
  success: boolean;
  statusCode: number;
  message: string;
  data: T;
  meta?: PaginationMeta;
};

type PaginationMeta = {
  page: number;
  limit: number;
  total: number;
  totalPages: number;
};

type ApiLookup = {
  id: number;
  name: string;
};

type ApiIssueStatus = ApiLookup & {
  code: string;
};

type ApiAgency = ApiLookup & {
  logo: string | null;
};

type ApiCdcIssueMatrixItem = {
  issueId: number;
  issueResolveId: number | null;
  meetingSummaryId: number | null;
  // OWN: raised by this role. MINISTRY: escalated by a ministry.
  source?: "OWN" | "MINISTRY";
  title: string;
  description: string;
  recommendation: string;
  category: ApiLookup;
  status: ApiIssueStatus;
  workingGroup: ApiLookup;
  primaryAgency: ApiAgency | null;
  // Every ministry on the issue, ordered. Optional so an older API response
  // still maps cleanly.
  governmentAgencies?: (ApiAgency & { agencyOrder: number })[] | null;
  rgcDecision: unknown;
  nextStep: unknown;
  remark: unknown;
  // Null means nobody has decided whether it goes to the plenary.
  escalation?: boolean | null;
  submittedAt: string;
};

type ApiCdcIssueMatrixDetail = {
  attachment?: UploadedFileMetadata | null;
  category?: ApiLookup | null;
  createdAt?: string | null;
  description?: string | null;
  user?: {
    id: number;
    name?: string | null;
    email?: string | null;
  } | null;
  rgcDecision?: unknown;
  nextStep?: unknown;
  indicators?: unknown;
  progressSolution?: unknown;
  implementationChallenges?: unknown;
  request?: unknown;
  sourceOfVerification?: string | null;
  linkToVerificationSource?: string | null;
  governmentAgencies?: {
    agencyOrder: number;
    stakeholder?: {
      id: number;
      name?: string | null;
      description?: string | null;
      logo?: string | null;
    } | null;
  }[];
  id: number;
  issueStatus?: ApiIssueStatus | null;
  meetingRequest?: {
    title?: string | null;
    meetings?:
      | {
          meetingDate?: string | null;
          startTime?: string | null;
          endTime?: string | null;
        }[]
      | null;
  } | null;
  recommendation?: string | null;
  stakeholder?: {
    id: number;
    name?: string | null;
  } | null;
  title?: string | null;
};

export type CdcIssueMatrixDetail = {
  attachment: UploadedFileMetadata | null;
  category: string;
  categoryId: number | null;
  description: string;
  rgcDecision: string;
  nextStep: string;
  indicators: string;
  progressSolution: string;
  implementationChallenges: string;
  request: string;
  sourceOfVerification: string;
  linkToVerificationSource: string;
  governmentAgencies: { agencyOrder: number; stakeholderId: number }[];
  id: number;
  issueStatusId: number | null;
  meetingDate: string | null;
  meetingRequestTitle: string | null;
  primaryAgencyLogo: string | null;
  primaryAgencyName: string;
  primaryAgencyDescription: string | null;
  recommendation: string;
  status: IssueStatus;
  submittedDate: string | null;
  submittedBy: string;
  title: string;
  workingGroupId: number;
  workingGroupName: string;
};

export type CdcIssueMatrixFormWorkingGroup = {
  id: number;
  name: string;
  logo?: string | null;
};

export type CdcIssueMatrixFormAgency = {
  id: number;
  name: string;
  logo?: string | null;
};

export type CdcIssueMatrixFormOptions = {
  agencies: CdcIssueMatrixFormAgency[];
  draftIssueStatusId: number;
  issueStatusId: number;
  workingGroups: CdcIssueMatrixFormWorkingGroup[];
};

const API_URL =
  process.env.NEXT_PUBLIC_API_URL || "http://localhost:3001/api/v1";
const BACKEND_ASSET_URL = API_URL.replace(/\/api(?:\/v\d+)?\/?$/, "");
const FETCH_LIMIT = 50;

function resolveAssetUrl(value: string | null | undefined) {
  if (!value) return null;

  if (
    value.startsWith("http://") ||
    value.startsWith("https://") ||
    value.startsWith("data:") ||
    value.startsWith("blob:")
  ) {
    return value;
  }

  if (value.startsWith("/uploads/")) {
    return `${BACKEND_ASSET_URL}${value}`;
  }

  return value;
}

const LOCAL_AGENCY_LOGOS: Record<string, string> = {
  MAFF: "/images/logo/maff.png",
  MEF: "/images/logo/mef.png",
  MISTI: "/images/logo/misti.png",
  MME: "/images/logo/mme.png",
  MoC: "/images/logo/commerce.png",
  MoE: "/images/logo/moe.png",
  MoEYS: "/images/logo/meys.png",
  MoH: "/images/logo/health.png",
  MoLVT: "/images/logo/mlvt.png",
  MoT: "/images/logo/tourism.png",
  MPWT: "/images/logo/mpwt.png",
  NBC: "/images/logo/nbc.png",
  NBFSA: "/images/logo/nbfsa.png",
  MLMUPC: "/images/logo/mlmupc.png",
};

function resolveAgencyLogo(name: string, logo: string | null | undefined) {
  return resolveAssetUrl(logo) ?? LOCAL_AGENCY_LOGOS[name.trim()] ?? null;
}

function stripHtml(value: string) {
  return value
    .replace(/<[^>]*>/g, " ")
    .replace(/&amp;/g, "&")
    .replace(/&nbsp;/g, " ")
    .replace(/&quot;/g, '"')
    .replace(/&#39;/g, "'")
    .replace(/\s+/g, " ")
    .trim();
}

function toPlainText(value: unknown): string {
  if (value === null || value === undefined) return "-";
  if (typeof value === "string") return stripHtml(value) || "-";
  if (typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }

  try {
    return stripHtml(JSON.stringify(value)) || "-";
  } catch {
    return "-";
  }
}

function toDetailText(value: unknown): string {
  if (value === null || value === undefined) return "";
  if (typeof value === "string") return stripHtml(value);
  if (typeof value === "number" || typeof value === "boolean") {
    return String(value);
  }

  try {
    return stripHtml(JSON.stringify(value));
  } catch {
    return "";
  }
}

function normalizeStatus(status: ApiIssueStatus): CdcIssueMatrixStatus {
  switch (status.code) {
    case "SOLVED":
      return "Solved";
    case "IN_PROGRESS":
      return "In Progress";
    case "NOT_ADDRESSED":
      return "Not Addressed";
    case "NEW_SUBMISSION":
      return "New Submission";
    case "DRAFT":
      return "Draft";
    case "SAVED":
      return "Saved";
    default:
      return "Not Addressed";
  }
}

function getYear(value: string) {
  const date = new Date(value);
  return Number.isNaN(date.getTime()) ? "-" : String(date.getFullYear());
}

function mapCdcIssueMatrixRow(item: ApiCdcIssueMatrixItem): CdcIssueMatrixRow {
  // Ordered ministries, primary first. The logos go through the same resolver
  // the ministry picker uses, so a row and its dropdown show the same image.
  const agencies = [...(item.governmentAgencies ?? [])]
    .sort(
      (firstAgency, secondAgency) =>
        firstAgency.agencyOrder - secondAgency.agencyOrder,
    )
    .map((agency) => ({
      id: agency.id,
      name: agency.name.trim() || "-",
      logo: resolveAgencyLogo(agency.name, agency.logo),
    }));
  const primaryAgency = agencies[0];

  return {
    id: item.issueId,
    issueId: item.issueId,
    issueResolveId: item.issueResolveId,
    meetingSummaryId: item.meetingSummaryId,
    source: item.source ?? "MINISTRY",
    workingGroupId: item.workingGroup.id,
    workingGroup: item.workingGroup.name.trim() || "-",
    issue: item.title.trim() || "-",
    category: item.category.name.trim() || "-",
    issueDescription: stripHtml(item.description) || "-",
    recommendation: stripHtml(item.recommendation) || "-",
    governmentDecision: toPlainText(item.rgcDecision),
    primaryAgency:
      primaryAgency?.name ?? item.primaryAgency?.name.trim() ?? "-",
    primaryAgencyImage:
      primaryAgency?.logo ??
      resolveAgencyLogo(
        item.primaryAgency?.name ?? "",
        item.primaryAgency?.logo,
      ),
    agencies,
    plenaryEscalation: item.escalation === true,
    status: normalizeStatus(item.status),
    statusId: item.status.id,
    nextStep: toPlainText(item.nextStep),
    remark: toPlainText(item.remark),
    submittedAt: item.submittedAt,
    year: getYear(item.submittedAt),
    action: "View",
  };
}

function formatMeetingDate(
  meetings:
    | {
        meetingDate?: string | null;
        startTime?: string | null;
        endTime?: string | null;
      }[]
    | null
    | undefined,
) {
  const meeting = Array.isArray(meetings)
    ? meetings.find((item) => item?.meetingDate)
    : null;

  if (!meeting?.meetingDate) return null;

  const date = new Date(meeting.meetingDate);
  if (Number.isNaN(date.getTime())) return null;

  return date.toISOString();
}

function mapCdcIssueMatrixDetail(issue: ApiCdcIssueMatrixDetail): CdcIssueMatrixDetail {
  const agencies = (issue.governmentAgencies ?? [])
    .map((agency) => ({
      agencyOrder: agency.agencyOrder,
      stakeholderId: agency.stakeholder?.id ?? 0,
    }))
    .filter((agency) => agency.stakeholderId > 0)
    .sort((first, second) => first.agencyOrder - second.agencyOrder);
  const primaryAgency = agencies[0];
  const primaryAgencyData = (issue.governmentAgencies ?? []).find(
    (agency) => agency.agencyOrder === primaryAgency?.agencyOrder,
  )?.stakeholder;

  return {
    attachment: issue.attachment
      ? {
          ...issue.attachment,
          path: resolveAssetUrl(issue.attachment.path) ?? issue.attachment.path,
        }
      : null,
    category: issue.category?.name ?? "-",
    categoryId: issue.category?.id ?? null,
    description: issue.description ?? "-",
    rgcDecision: toDetailText(issue.rgcDecision),
    nextStep: toDetailText(issue.nextStep),
    indicators: toDetailText(issue.indicators),
    progressSolution: toDetailText(issue.progressSolution),
    implementationChallenges: toDetailText(issue.implementationChallenges),
    request: toDetailText(issue.request),
    sourceOfVerification: issue.sourceOfVerification ?? "",
    linkToVerificationSource: issue.linkToVerificationSource ?? "",
    governmentAgencies: agencies,
    id: issue.id,
    issueStatusId: issue.issueStatus?.id ?? null,
    meetingDate: formatMeetingDate(issue.meetingRequest?.meetings),
    meetingRequestTitle: issue.meetingRequest?.title ?? null,
    primaryAgencyLogo: resolveAgencyLogo(
      primaryAgencyData?.name ?? "",
      primaryAgencyData?.logo,
    ),
    primaryAgencyName: primaryAgencyData?.name ?? "-",
    primaryAgencyDescription: primaryAgencyData?.description?.trim() || null,
    recommendation: issue.recommendation ?? "-",
    status: normalizeStatus(
      issue.issueStatus ?? { id: 0, code: "DRAFT", name: "Draft" },
    ),
    submittedDate: issue.createdAt ?? null,
    submittedBy: issue.user?.name?.trim() || issue.user?.email?.trim() || "-",
    title: issue.title ?? "-",
    workingGroupId: issue.stakeholder?.id ?? 0,
    workingGroupName: issue.stakeholder?.name ?? "",
  };
}

function getApiErrorMessage(body: unknown, status: number) {
  if (body && typeof body === "object" && "message" in body) {
    const message = (body as { message?: unknown }).message;

    if (Array.isArray(message)) return message.join(" ");
    if (typeof message === "string" && message.trim()) return message;
  }

  if (status === 401) return "Unauthorized";
  if (status === 403) return "You do not have permission to view CDC issues.";

  return `Unable to load CDC issue matrix (${status}).`;
}

function getRowUpdateErrorMessage(body: unknown, status: number) {
  if (status === 403) {
    return "You do not have permission to update this issue.";
  }

  return getApiErrorMessage(body, status);
}

async function parseResponseBody(response: Response) {
  const text = await response.text();
  if (!text) return null;

  try {
    return JSON.parse(text) as unknown;
  } catch {
    return text;
  }
}

async function getRequest<T>(path: string): Promise<T> {
  const response = await fetch(`${API_URL}${path}`, {
    method: "GET",
    credentials: "include",
    headers: {
      "Content-Type": "application/json",
    },
  });
  const responseBody = await parseResponseBody(response);

  if (!response.ok) {
    if (response.status === 401) {
      redirectToLoginAfterSessionExpired();
    }

    throw new Error(getApiErrorMessage(responseBody, response.status));
  }

  return responseBody as T;
}

async function formRequest<T>(
  path: string,
  method: "POST" | "PATCH",
  formData: FormData,
): Promise<T> {
  const response = await fetch(`${API_URL}${path}`, {
    method,
    credentials: "include",
    body: formData,
  });
  const responseBody = await parseResponseBody(response);

  if (!response.ok) {
    if (response.status === 401) {
      redirectToLoginAfterSessionExpired();
    }

    throw new Error(getApiErrorMessage(responseBody, response.status));
  }

  return responseBody as T;
}

async function deleteRequest<T>(path: string): Promise<T> {
  const response = await fetch(`${API_URL}${path}`, {
    method: "DELETE",
    credentials: "include",
  });
  const responseBody = await parseResponseBody(response);

  if (!response.ok) {
    if (response.status === 401) {
      redirectToLoginAfterSessionExpired();
    }

    throw new Error(getApiErrorMessage(responseBody, response.status));
  }

  return responseBody as T;
}

async function getCdcIssueMatrixPage(page: number) {
  return getRequest<ApiResponse<ApiCdcIssueMatrixItem[]>>(
    `/issues?page=${page}&limit=${FETCH_LIMIT}`,
  );
}

export async function getCdcIssueMatrixIssues() {
  const firstPage = await getCdcIssueMatrixPage(1);
  const totalPages = firstPage.meta?.totalPages ?? 1;

  if (totalPages <= 1) {
    return (firstPage.data ?? []).map(mapCdcIssueMatrixRow);
  }

  const remainingPageNumbers = Array.from(
    { length: totalPages - 1 },
    (_, index) => index + 2,
  );
  const remainingPages = await Promise.all(
    remainingPageNumbers.map(getCdcIssueMatrixPage),
  );
  const allItems = [
    ...(firstPage.data ?? []),
    ...remainingPages.flatMap((response) => response.data ?? []),
  ];

  return allItems.map(mapCdcIssueMatrixRow);
}

export type UpdateCdcIssueMatrixRowPayload = {
  /** One ministry id; it becomes the primary agency. */
  governmentAgencyIds?: number[];
  issueStatusId?: number;
  /** True flags the issue for the plenary, false takes the flag off. */
  escalation?: boolean;
};

/**
 * Change the ministries and/or the status of one row, straight from the table.
 * Requires the "update CdcIssueMatrix" permission the CDC Secretariat holds.
 */
export async function updateCdcIssueMatrixRow(
  issueId: number,
  payload: UpdateCdcIssueMatrixRowPayload,
) {
  const response = await fetch(`${API_URL}/issues/${issueId}`, {
    method: "PATCH",
    credentials: "include",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(payload),
  });
  const responseBody = await parseResponseBody(response);

  if (!response.ok) {
    if (response.status === 401) {
      redirectToLoginAfterSessionExpired();
    }

    throw new Error(getRowUpdateErrorMessage(responseBody, response.status));
  }

  return (responseBody as ApiResponse<ApiCdcIssueMatrixItem>).data;
}

type ApiIssueStatusLookup = {
  id: number;
  code?: string | null;
  name?: string | null;
};

async function getCdcIssueMatrixFormOptions() {
  const [workingGroupsResponse, agenciesResponse, statusesResponse] =
    await Promise.all([
      getRequest<ApiResponse<CdcIssueMatrixFormWorkingGroup[]>>(
        "/stakeholders/working-groups",
      ),
      getRequest<ApiResponse<CdcIssueMatrixFormAgency[]>>(
        "/working-group-issues/government-agencies",
      ),
      getRequest<ApiResponse<ApiIssueStatusLookup[]>>(
        "/working-group-issues/statuses",
      ),
    ]);

  const savedStatus = (statusesResponse.data ?? []).find(
    (status) => status.code?.toUpperCase() === "SAVED",
  );
  const draftStatus = (statusesResponse.data ?? []).find(
    (status) => status.code?.toUpperCase() === "DRAFT",
  );
  const issueStatusId = savedStatus?.id ?? draftStatus?.id ?? 0;

  return {
    agencies: (agenciesResponse.data ?? []).map((agency) => ({
      ...agency,
      logo: resolveAgencyLogo(agency.name, agency.logo),
    })),
    draftIssueStatusId: draftStatus?.id ?? issueStatusId,
    issueStatusId,
    workingGroups: (workingGroupsResponse.data ?? []).map((workingGroup) => ({
      ...workingGroup,
      logo: resolveAssetUrl(workingGroup.logo),
    })),
  };
}

export { getCdcIssueMatrixFormOptions };

export type CreateCdcIssuePayload = {
  attachmentFile?: File | null;
  categoryId?: number;
  description: string;
  governmentAgencies: { agencyOrder: number; stakeholderId: number }[];
  recommendation: string;
  workingGroupId: number;
  title: string;
  issueStatusId: number;
};

export async function createCdcIssue(payload: CreateCdcIssuePayload) {
  const formData = new FormData();
  formData.append("workingGroupId", String(payload.workingGroupId));
  formData.append("title", payload.title);
  formData.append("description", payload.description);
  formData.append("recommendation", payload.recommendation);
  formData.append("issueStatusId", String(payload.issueStatusId));
  if (payload.categoryId !== undefined) {
    formData.append("categoryId", String(payload.categoryId));
  }
  formData.append(
    "governmentAgencies",
    JSON.stringify(payload.governmentAgencies),
  );

  if (payload.attachmentFile) {
    formData.append("attachmentFile", payload.attachmentFile);
  }

  const response = await formRequest<ApiResponse<unknown>>(
    "/issues",
    "POST",
    formData,
  );

  return response.message;
}

export async function getCdcIssueMatrixDetail(issueId: number) {
  const response = await getRequest<ApiResponse<ApiCdcIssueMatrixDetail>>(
    `/issues/${issueId}`,
  );

  return mapCdcIssueMatrixDetail(response.data);
}

export type UpdateCdcIssuePayload = Omit<
  CreateCdcIssuePayload,
  "workingGroupId"
>;

export async function updateCdcIssue(
  issueId: number,
  payload: UpdateCdcIssuePayload,
) {
  const formData = new FormData();
  formData.append("title", payload.title);
  formData.append("description", payload.description);
  formData.append("recommendation", payload.recommendation);
  formData.append("issueStatusId", String(payload.issueStatusId));

  if (payload.categoryId !== undefined) {
    formData.append("categoryId", String(payload.categoryId));
  }

  formData.append(
    "governmentAgencies",
    JSON.stringify(payload.governmentAgencies),
  );

  if (payload.attachmentFile) {
    formData.append("attachmentFile", payload.attachmentFile);
  }

  const response = await formRequest<ApiResponse<unknown>>(
    `/issues/${issueId}`,
    "PATCH",
    formData,
  );

  return response.message;
}

export async function deleteCdcIssue(issueId: number) {
  const response = await deleteRequest<ApiResponse<unknown>>(
    `/issues/${issueId}`,
  );

  return response.message;
}

export async function getCdcIssueMatrixSummary() {
  const response = await getRequest<ApiResponse<CdcIssueMatrixSummary>>(
    "/issues/summary",
  );

  return response.data;
}
