import { redirectToLoginAfterSessionExpired } from "@/features/auth/service/auth-service";
import type {
  WorkingGroupIssueAgency,
  WorkingGroupIssueLookup,
} from "@/features/pswg/working-group-issues/service/working-group-issues-service";

import type {
  IssueMatrixRow,
  IssueMatrixStatus,
  IssueMatrixSummary,
} from "../components/issue-matrix-data";
import type { IssueMatrixLanguage } from "../components/issue-matrix-i18n";

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

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

type PaginatedData<T> = {
  items: T[];
  meta: PaginationMeta;
};

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

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

type ApiMeeting = {
  meetingDate?: string | null;
  documentReference?: string | null;
};

type ApiAttachment =
  | string
  | {
      path?: string | null;
      name?: string | null;
      size?: number | null;
      mimeType?: string | null;
    };

type ApiIssue = {
  id: number;
  source?: "CDC" | "MINISTRY" | null;
  title?: string | null;
  description?: string | null;
  recommendation?: string | null;
  attachment?: ApiAttachment | null;
  createdAt?: string | null;
  updatedAt?: string | null;
  issueStatus?: ApiIssueStatus | null;
  // The plenary flag is a column on the issue. Null means undecided.
  escalation?: boolean | null;
  rgcDecision?: unknown;
  nextStep?: unknown;
  indicators?: unknown;
  progressSolution?: unknown;
  implementationChallenges?: unknown;
  sourceOfVerification?: unknown;
  linkToVerificationSource?: string | null;
  dateOfIssueSolution?: string | null;
  category?: {
    id: number;
    name?: string | null;
  } | null;
  stakeholder?: ApiStakeholder | null;
  meetingRequest?: {
    title?: string | null;
    meetings?: ApiMeeting[];
  } | null;
  governmentAgencies?: {
    agencyOrder: number;
    stakeholder?: ApiStakeholder | null;
  }[];
};

export type ExportIssueMatrixParams = {
  language: IssueMatrixLanguage;
  search: string;
  selectedWorkingGroups: string[];
  selectedStatuses: string[];
  selectedCategories: string[];
  selectedPrimaryAgencies: string[];
  selectedYears: string[];
  selectedAttachments: string[];
  rows: IssueMatrixRow[];
  statuses: WorkingGroupIssueLookup[];
  categories: WorkingGroupIssueLookup[];
  agencies: WorkingGroupIssueAgency[];
};

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 MATRIX_FETCH_LIMIT = 50;

const ISSUE_STATUS_BY_CODE: Record<string, IssueMatrixStatus> = {
  DRAFT: "Draft",
  SAVED: "Saved",
  NEW_SUBMISSION: "New Submission",
  IN_PROGRESS: "In Progress",
  SOLVED: "Solved",
  NOT_ADDRESSED: "Not Addressed",
};

const ISSUE_STATUS_BY_NAME: Record<string, IssueMatrixStatus> = {
  draft: "Draft",
  saved: "Saved",
  "new submission": "New Submission",
  "in progress": "In Progress",
  solved: "Solved",
  "not addressed": "Not Addressed",
};

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;
}

function getAttachmentPath(attachment?: ApiAttachment | null) {
  if (typeof attachment === "string") {
    return attachment.trim() || null;
  }

  return attachment?.path?.trim() || null;
}

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 === 400) return "Please check the issue matrix request.";
  if (status === 401) return "Unauthorized";
  if (status === 403) return "You do not have permission for this action.";
  if (status === 404) return "Issue was not found.";

  return `Request failed (${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 jsonRequest<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;
}

function findLookupIdsByNames(
  list: Array<{ id: number; name: string }>,
  names: string[],
): number[] {
  return names
    .map((name) => {
      const item = list.find(
        (entry) => entry.name.toLowerCase() === name.toLowerCase(),
      );

      return item?.id;
    })
    .filter((id): id is number => id !== undefined);
}

function findWorkingGroupIdsByNames(rows: IssueMatrixRow[], names: string[]) {
  const selectedNames = new Set(names.map((name) => name.toLowerCase()));

  return Array.from(
    new Set(
      rows
        .filter((row) => selectedNames.has(row.workingGroup.toLowerCase()))
        .map((row) => row.workingGroupId)
        .filter((workingGroupId): workingGroupId is number =>
          Number.isInteger(workingGroupId),
        ),
    ),
  );
}

function buildIssueMatrixExportQuery(params: ExportIssueMatrixParams) {
  const searchParams = new URLSearchParams();
  searchParams.set("lang", params.language === "kh" ? "km" : "en");

  const normalizedSearch = params.search.trim();
  if (normalizedSearch) {
    searchParams.set("search", normalizedSearch);
  }

  const ownerStakeholderIds = findWorkingGroupIdsByNames(
    params.rows,
    params.selectedWorkingGroups,
  );
  ownerStakeholderIds.forEach((id) => {
    searchParams.append("ownerStakeholderIds", String(id));
  });

  const issueStatusIds = findLookupIdsByNames(
    params.statuses,
    params.selectedStatuses,
  );
  issueStatusIds.forEach((id) => {
    searchParams.append("issueStatusIds", String(id));
  });

  const categoryIds = findLookupIdsByNames(
    params.categories,
    params.selectedCategories,
  );
  categoryIds.forEach((id) => {
    searchParams.append("categoryIds", String(id));
  });

  const primaryAgencyIds = findLookupIdsByNames(
    params.agencies,
    params.selectedPrimaryAgencies,
  );
  primaryAgencyIds.forEach((id) => {
    searchParams.append("primaryAgencyIds", String(id));
  });

  params.selectedYears.forEach((year) => {
    const parsedYear = Number(year);
    if (Number.isInteger(parsedYear)) {
      searchParams.append("years", String(parsedYear));
    }
  });

  if (params.selectedAttachments.length === 1) {
    searchParams.set(
      "hasAttachment",
      params.selectedAttachments[0] === "Yes" ? "true" : "false",
    );
  }

  const query = searchParams.toString();
  return query ? `?${query}` : "";
}

function getExportFilename(contentDisposition: string | null) {
  if (!contentDisposition) {
    return "issue-matrix.xlsx";
  }

  const match = /filename="([^"]+)"/i.exec(contentDisposition);
  return match?.[1] ?? "issue-matrix.xlsx";
}

function triggerBrowserDownload(blob: Blob, filename: string) {
  const objectUrl = URL.createObjectURL(blob);
  const anchor = document.createElement("a");
  anchor.href = objectUrl;
  anchor.download = filename;
  anchor.click();
  URL.revokeObjectURL(objectUrl);
}

function normalizeIssueMatrixStatus(
  status?: ApiIssueStatus | null,
): IssueMatrixStatus {
  if (status?.code) {
    const normalizedCode = status.code.trim().toUpperCase();
    const fromCode = ISSUE_STATUS_BY_CODE[normalizedCode];

    if (fromCode) {
      return fromCode;
    }
  }

  if (status?.name) {
    const normalizedName = status.name.trim().toLowerCase();
    const fromName = ISSUE_STATUS_BY_NAME[normalizedName];

    if (fromName) {
      return fromName;
    }
  }

  return "Draft";
}

function getDisplayValue(value: unknown) {
  if (value === null || value === undefined) {
    return "-";
  }

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

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

function hasValue(value: unknown) {
  if (value === null || value === undefined) {
    return false;
  }

  return typeof value === "string" ? value.trim().length > 0 : true;
}

function hasProgressReport(issue: ApiIssue) {
  return [
    issue.indicators,
    issue.progressSolution,
    issue.implementationChallenges,
    issue.sourceOfVerification,
    issue.linkToVerificationSource,
    issue.dateOfIssueSolution,
  ].some(hasValue);
}

function getIssueYear(value: string | null | undefined) {
  if (!value) {
    return "-";
  }

  const date = new Date(value);

  if (Number.isNaN(date.getTime())) {
    return "-";
  }

  return String(date.getFullYear());
}

function getAgencyName(
  agencies: ApiIssue["governmentAgencies"],
  agencyOrder: number,
) {
  return (
    agencies?.find((agency) => agency.agencyOrder === agencyOrder)?.stakeholder
      ?.name ?? "-"
  );
}

function getAgencyLogo(
  agencies: ApiIssue["governmentAgencies"],
  agencyOrder: number,
) {
  const logo = agencies?.find((agency) => agency.agencyOrder === agencyOrder)
    ?.stakeholder?.logo;

  return resolveAssetUrl(logo);
}

function getPrimaryAgency(agencies: ApiIssue["governmentAgencies"]) {
  return agencies?.find((agency) => agency.agencyOrder === 1)?.stakeholder;
}

function mapIssueMatrixRow(issue: ApiIssue): IssueMatrixRow {
  const agencies = [...(issue.governmentAgencies ?? [])].sort(
    (firstAgency, secondAgency) =>
      firstAgency.agencyOrder - secondAgency.agencyOrder,
  );
  const primaryAgency = getPrimaryAgency(agencies);
  const attachmentPath = getAttachmentPath(issue.attachment);
  const meeting = issue.meetingRequest?.meetings?.[0];
  const meetingDate = meeting?.meetingDate ?? null;
  const meetingReferenceDocument = resolveAssetUrl(meeting?.documentReference);

  return {
    id: issue.id,
    source: issue.source === "CDC" ? "CDC" : "MINISTRY",
    workingGroupId: issue.stakeholder?.id ?? null,
    issue: issue.title?.trim() || "-",
    category: issue.category?.name?.trim() || "-",
    issueDescription: issue.description?.trim() || "-",
    recommendation: issue.recommendation?.trim() || "-",
    governmentDecision: getDisplayValue(issue.rgcDecision),
    status: normalizeIssueMatrixStatus(issue.issueStatus),
    plenaryEscalation: issue.escalation === true,
    statusCode: issue.issueStatus?.code?.trim().toUpperCase() || "DRAFT",
    statusId: issue.issueStatus?.id ?? null,
    dateOfSolution: issue.dateOfIssueSolution || "-",
    primaryAgency: primaryAgency?.name?.trim() || "-",
    primaryAgencyId: primaryAgency?.id ?? null,
    primaryAgencyImage: resolveAssetUrl(primaryAgency?.logo),
    submittedDate: issue.createdAt ?? null,
    meetingDate,
    meetingReferenceDocument,
    attachment: resolveAssetUrl(attachmentPath),
    meetingType: "Working Group",
    wgMeetingDate: meetingDate || "-",
    secondAgency: getAgencyName(agencies, 2),
    secondAgencyImage: getAgencyLogo(agencies, 2),
    thirdAgency: getAgencyName(agencies, 3),
    thirdAgencyImage: getAgencyLogo(agencies, 3),
    fourthAgency: getAgencyName(agencies, 4),
    fourthAgencyImage: getAgencyLogo(agencies, 4),
    fifthAgency: getAgencyName(agencies, 5),
    fifthAgencyImage: getAgencyLogo(agencies, 5),
    indicator: getDisplayValue(issue.indicators),
    sourceOfVerification: getDisplayValue(issue.sourceOfVerification),
    verificationLink: issue.linkToVerificationSource?.trim() || "No data",
    action: "View",
    workingGroup: issue.stakeholder?.name?.trim() || "-",
    year: getIssueYear(issue.createdAt),
    progressReport: hasProgressReport(issue) ? "Yes" : "No",
    hasAttachment: Boolean(attachmentPath),
  };
}

export type UpdateIssueMatrixRowPayload = {
  governmentAgencyIds?: number[];
  issueStatusId?: number;
};

export async function updateIssueMatrixRow(
  issueId: number,
  payload: UpdateIssueMatrixRowPayload,
) {
  const response = await fetch(
    `${API_URL}/working-group-issues/issue-matrix/${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(getApiErrorMessage(responseBody, response.status));
  }

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

export async function updateIssueMatrixPlenaryEscalation(
  issueId: number,
  escalated: boolean,
) {
  // The plenary flag is a field on the issue now, so it rides on the shared
  // issue update rather than an endpoint of its own.
  const response = await fetch(`${API_URL}/issues/${issueId}`, {
    method: "PATCH",
    credentials: "include",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ escalation: escalated }),
  });

  const responseBody = await parseResponseBody(response);

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

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

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

export async function getIssueMatrixIssues() {
  const response = await jsonRequest<ApiResponse<PaginatedData<ApiIssue>>>(
    `/working-group-issues/issue-matrix?limit=${MATRIX_FETCH_LIMIT}`,
  );

  const items = response.data?.items;

  return Array.isArray(items) ? items.map(mapIssueMatrixRow) : [];
}

export async function getIssueMatrixIssueById(id: number) {
  const response = await jsonRequest<ApiResponse<ApiIssue>>(
    `/working-group-issues/${id}`,
  );

  return mapIssueMatrixRow(response.data);
}

export async function getIssueMatrixSummary() {
  const response = await jsonRequest<ApiResponse<IssueMatrixSummary>>(
    "/working-group-issues/issue-matrix/summary",
  );

  return response.data;
}

export async function exportIssueMatrix(params: ExportIssueMatrixParams) {
  const query = buildIssueMatrixExportQuery(params);
  const response = await fetch(
    `${API_URL}/working-group-issues/issue-matrix/export${query}`,
    {
      method: "GET",
      credentials: "include",
    },
  );

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

    const responseBody = await parseResponseBody(response);
    throw new Error(getApiErrorMessage(responseBody, response.status));
  }

  const blob = await response.blob();
  const filename = getExportFilename(
    response.headers.get("Content-Disposition"),
  );

  triggerBrowserDownload(blob, filename);
}
