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 = {
  code?: string | null;
  name?: string | null;
};

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

type ApiIssue = {
  id: number;
  title?: string | null;
  description?: string | null;
  recommendation?: string | null;
  attachment?: string | null;
  createdAt?: string | null;
  updatedAt?: string | null;
  issueStatus?: ApiIssueStatus | null;
  category?: {
    id: number;
    name?: string | null;
  } | null;
  stakeholder?: ApiStakeholder | null;
  meetingRequest?: {
    title?: string | null;
  } | 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",
  NEW_SUBMISSION: "New Submission",
  IN_PROGRESS: "In Progress",
  SOLVED: "Solved",
  NOT_ADDRESSED: "Not Addressed",
};

const ISSUE_STATUS_BY_NAME: Record<string, IssueMatrixStatus> = {
  draft: "Draft",
  "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 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 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);

  return {
    id: issue.id,
    workingGroupId: issue.stakeholder?.id ?? null,
    issue: issue.title?.trim() || "-",
    category: issue.category?.name?.trim() || "-",
    issueDescription: issue.description?.trim() || "-",
    recommendation: issue.recommendation?.trim() || "-",
    governmentDecision: "-",
    status: normalizeIssueMatrixStatus(issue.issueStatus),
    dateOfSolution: "-",
    primaryAgency: primaryAgency?.name?.trim() || "-",
    primaryAgencyImage: resolveAssetUrl(primaryAgency?.logo),
    submittedDate: issue.createdAt ?? null,
    meetingDate: issue.meetingRequest?.title?.trim() || null,
    attachment: resolveAssetUrl(issue.attachment),
    meetingType: "Working Group",
    wgMeetingDate: "-",
    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: "-",
    sourceOfVerification: "-",
    verificationLink: issue.attachment?.trim() ? "Download" : "No data",
    action: "View",
    workingGroup: issue.stakeholder?.name?.trim() || "-",
    year: getIssueYear(issue.createdAt),
    progressReport: "-",
    hasAttachment: Boolean(issue.attachment?.trim()),
  };
}

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