"use client";

import { useLogger } from "@pythnetwork/app-logger";
import { Badge } from "@pythnetwork/component-library/Badge";
import { Button } from "@pythnetwork/component-library/Button";
import { Card } from "@pythnetwork/component-library/Card";
import { Paginator } from "@pythnetwork/component-library/Paginator";
import { SearchInput } from "@pythnetwork/component-library/SearchInput";
import { Select } from "@pythnetwork/component-library/Select";
import { SingleToggleGroup } from "@pythnetwork/component-library/SingleToggleGroup";
import {
  type RowConfig,
  type SortDescriptor,
  Table,
} from "@pythnetwork/component-library/Table";
import { useQueryState, parseAsStringEnum, parseAsBoolean } from "nuqs";
import { type ReactNode, Suspense, useMemo, useCallback } from "react";
import { useFilter, useCollator } from "react-aria";

import styles from "./index.module.scss";
import { useQueryParamFilterPagination } from "../../hooks/use-query-param-filter-pagination";
import { Cluster } from "../../services/pyth";
import {
  type StatusName,
  STATUS_NAMES,
  Status as StatusType,
  statusNameToStatus,
} from "../../status";
import { Explain } from "../Explain";
import { EvaluationTime } from "../Explanations";
import { FormattedNumber } from "../FormattedNumber";
import { LivePrice, LiveConfidence, LiveComponentValue } from "../LivePrices";
import { NoResults } from "../NoResults";
import rootStyles from "../Root/index.module.scss";
import { Score } from "../Score";
import { Status as StatusComponent } from "../Status";

const SCORE_WIDTH = 32;

type Props = {
  className?: string | undefined;
  priceComponents: PriceComponent[];
  metricsTime?: Date | undefined;
  nameLoadingSkeleton: ReactNode;
  label: string;
  searchPlaceholder: string;
  onPriceComponentAction: (component: PriceComponent) => void;
};

type PriceComponent = {
  id: string;
  score: number | undefined;
  symbol: string;
  uptimeScore: number | undefined;
  deviationScore: number | undefined;
  stalledScore: number | undefined;
  cluster: Cluster;
  status: StatusType;
  feedKey: string;
  publisherKey: string;
  name: ReactNode;
  nameAsString: string;
};

export const PriceComponentsCard = ({
  priceComponents,
  onPriceComponentAction,
  ...props
}: Props) => (
  <Suspense fallback={<PriceComponentsCardContents isLoading {...props} />}>
    <ResolvedPriceComponentsCard
      priceComponents={priceComponents}
      onPriceComponentAction={onPriceComponentAction}
      {...props}
    />
  </Suspense>
);

export const ResolvedPriceComponentsCard = ({
  priceComponents,
  onPriceComponentAction,
  ...props
}: Props) => {
  const logger = useLogger();
  const collator = useCollator();
  const filter = useFilter({ sensitivity: "base", usage: "search" });
  const [status, setStatus] = useQueryState(
    "status",
    parseAsStringEnum(["", ...Object.values(STATUS_NAMES)]).withDefault(""),
  );
  const [showQuality, setShowQuality] = useQueryState(
    "showQuality",
    parseAsBoolean.withDefault(false),
  );
  const statusType = useMemo(() => statusNameToStatus(status), [status]);
  const componentsFilteredByStatus = useMemo(
    () =>
      statusType === undefined
        ? priceComponents
        : priceComponents.filter(
            (component) => component.status === statusType,
          ),
    [statusType, priceComponents],
  );

  const {
    search,
    sortDescriptor,
    page,
    pageSize,
    updateSearch,
    updateSortDescriptor,
    updatePage,
    updatePageSize,
    paginatedItems,
    numResults,
    numPages,
    mkPageLink,
  } = useQueryParamFilterPagination(
    componentsFilteredByStatus,
    (component, search) => filter.contains(component.nameAsString, search),
    (a, b, { column, direction }) => {
      switch (column) {
        case "score":
        case "uptimeScore":
        case "deviationScore":
        case "stalledScore": {
          if (a[column] === undefined && b[column] === undefined) {
            return 0;
          } else if (a[column] === undefined) {
            return direction === "descending" ? 1 : -1;
          } else if (b[column] === undefined) {
            return direction === "descending" ? -1 : 1;
          } else {
            return (
              (direction === "descending" ? -1 : 1) * (a[column] - b[column])
            );
          }
        }

        case "name": {
          return (
            (direction === "descending" ? -1 : 1) *
            collator.compare(a.nameAsString, b.nameAsString)
          );
        }

        case "status": {
          const resultByStatus = b.status - a.status;
          const result =
            resultByStatus === 0
              ? collator.compare(a.nameAsString, b.nameAsString)
              : resultByStatus;

          return (direction === "descending" ? -1 : 1) * result;
        }

        default: {
          return 0;
        }
      }
    },
    {
      defaultPageSize: 20,
      defaultSort: "name",
      defaultDescending: false,
    },
  );

  const rows = useMemo(
    () =>
      paginatedItems.map((component) => ({
        id: component.id,
        data: {
          name: (
            <div className={styles.componentName}>
              {component.name}
              {component.cluster === Cluster.PythtestConformance && (
                <Badge variant="muted" style="filled" size="xs">
                  test
                </Badge>
              )}
            </div>
          ),
          ...(showQuality
            ? {
                score: component.score !== undefined && (
                  <Score score={component.score} width={SCORE_WIDTH} />
                ),
                uptimeScore: component.uptimeScore !== undefined && (
                  <FormattedNumber
                    value={component.uptimeScore}
                    maximumSignificantDigits={5}
                  />
                ),
                deviationScore: component.deviationScore !== undefined && (
                  <FormattedNumber
                    value={component.deviationScore}
                    maximumSignificantDigits={5}
                  />
                ),
                stalledScore: component.stalledScore !== undefined && (
                  <FormattedNumber
                    value={component.stalledScore}
                    maximumSignificantDigits={5}
                  />
                ),
              }
            : {
                slot: (
                  <LiveComponentValue
                    feedKey={component.feedKey}
                    publisherKey={component.publisherKey}
                    field="publishSlot"
                  />
                ),
                price: (
                  <LivePrice
                    feedKey={component.feedKey}
                    publisherKey={component.publisherKey}
                  />
                ),
                confidence: (
                  <LiveConfidence
                    feedKey={component.feedKey}
                    publisherKey={component.publisherKey}
                  />
                ),
              }),
          status: <StatusComponent status={component.status} />,
        },
        onAction: () => {
          onPriceComponentAction(component);
        },
      })),
    [paginatedItems, showQuality, onPriceComponentAction],
  );

  const updateStatus = useCallback(
    (newStatus: StatusName | "") => {
      updatePage(1);
      setStatus(newStatus).catch((error: unknown) => {
        logger.error("Failed to update status", error);
      });
    },
    [updatePage, setStatus, logger],
  );

  const updateShowQuality = useCallback(
    (newValue: boolean) => {
      setShowQuality(newValue).catch((error: unknown) => {
        logger.error("Failed to update show quality", error);
      });
    },
    [setShowQuality, logger],
  );

  return (
    <PriceComponentsCardContents
      numResults={numResults}
      search={search}
      sortDescriptor={sortDescriptor}
      numPages={numPages}
      page={page}
      pageSize={pageSize}
      onSearchChange={updateSearch}
      onSortChange={updateSortDescriptor}
      onPageSizeChange={updatePageSize}
      onPageChange={updatePage}
      mkPageLink={mkPageLink}
      rows={rows}
      status={status}
      onStatusChange={updateStatus}
      showQuality={showQuality}
      setShowQuality={updateShowQuality}
      {...props}
    />
  );
};

type PriceComponentsCardProps = Pick<
  Props,
  | "className"
  | "metricsTime"
  | "nameLoadingSkeleton"
  | "label"
  | "searchPlaceholder"
> &
  (
    | { isLoading: true }
    | {
        isLoading?: false;
        numResults: number;
        search: string;
        sortDescriptor: SortDescriptor;
        numPages: number;
        page: number;
        pageSize: number;
        onSearchChange: (newSearch: string) => void;
        onSortChange: (newSort: SortDescriptor) => void;
        onPageSizeChange: (newPageSize: number) => void;
        onPageChange: (newPage: number) => void;
        mkPageLink: (page: number) => string;
        status: StatusName | "";
        onStatusChange: (newStatus: StatusName | "") => void;
        showQuality: boolean;
        setShowQuality: (newValue: boolean) => void;
        rows: RowConfig<string>[];
      }
  );

export const PriceComponentsCardContents = ({
  className,
  metricsTime,
  nameLoadingSkeleton,
  label,
  searchPlaceholder,
  ...props
}: PriceComponentsCardProps) => {
  const collator = useCollator();
  return (
    <Card
      className={className}
      title={
        <>
          <span>{label}</span>
          {!props.isLoading && (
            <Badge style="filled" variant="neutral" size="md">
              {props.numResults}
            </Badge>
          )}
        </>
      }
      toolbar={
        <>
          <Select<StatusName | "">
            label="Status"
            size="sm"
            variant="outline"
            hideLabel
            options={[
              "",
              ...Object.values(STATUS_NAMES).toSorted((a, b) =>
                collator.compare(a, b),
              ),
            ]}
            {...(props.isLoading
              ? { isPending: true, buttonLabel: "Status" }
              : {
                  show: (value) => (value === "" ? "All" : value),
                  placement: "bottom end",
                  buttonLabel: props.status === "" ? "Status" : props.status,
                  selectedKey: props.status,
                  onSelectionChange: props.onStatusChange,
                })}
          />
          <SearchInput
            size="sm"
            width={60}
            placeholder={searchPlaceholder}
            {...(props.isLoading
              ? { isPending: true, isDisabled: true }
              : {
                  value: props.search,
                  onChange: props.onSearchChange,
                })}
          />
          <SingleToggleGroup
            {...(!props.isLoading && {
              selectedKey: props.showQuality ? "quality" : "prices",
              onSelectionChange: (newValue) => {
                props.setShowQuality(newValue === "quality");
              },
            })}
            items={[
              { id: "prices", children: "Prices" },
              { id: "quality", children: "Quality" },
            ]}
          />
        </>
      }
      {...(!props.isLoading && {
        footer: (
          <Paginator
            numPages={props.numPages}
            currentPage={props.page}
            onPageChange={props.onPageChange}
            pageSize={props.pageSize}
            onPageSizeChange={props.onPageSizeChange}
            pageSizeOptions={[10, 20, 30, 40, 50]}
            mkPageLink={props.mkPageLink}
          />
        ),
      })}
    >
      <Table
        label={label}
        fill
        rounded
        stickyHeader={rootStyles.headerHeight}
        columns={[
          {
            id: "name",
            name: "NAME / ID",
            alignment: "left",
            isRowHeader: true,
            loadingSkeleton: nameLoadingSkeleton,
            allowsSorting: true,
          },
          ...otherColumns(props),
          {
            id: "status",
            width: 20,
            name: (
              <>
                STATUS
                <Explain size="xs" title="Status">
                  A publisher{"'"}s feed have one of the following statuses:
                  <ul>
                    <li>
                      <b>Active</b> feeds have better than 50% uptime over the
                      last day
                    </li>
                    <li>
                      <b>Inactive</b> feeds have worse than 50% uptime over the
                      last day
                    </li>
                    <li>
                      <b>Unranked</b> feeds have not yet been evaluated by Pyth
                    </li>
                  </ul>
                  {metricsTime && <EvaluationTime scoreTime={metricsTime} />}
                </Explain>
              </>
            ),
            alignment: "right",
            allowsSorting: true,
          },
        ]}
        {...(props.isLoading
          ? { isLoading: true }
          : {
              rows: props.rows,
              sortDescriptor: props.sortDescriptor,
              onSortChange: props.onSortChange,
              emptyState: (
                <NoResults
                  query={props.search}
                  onClearSearch={() => {
                    props.onSearchChange("");
                    props.onStatusChange("");
                  }}
                />
              ),
            })}
      />
    </Card>
  );
};

const otherColumns = ({
  metricsTime,
  ...props
}: { metricsTime?: Date | undefined } & (
  | { isLoading: true }
  | { isLoading?: false; showQuality: boolean }
)) => {
  if (props.isLoading) {
    return [];
  } else {
    return props.showQuality
      ? [
          {
            id: "uptimeScore",
            width: 20,
            name: (
              <>
                UPTIME SCORE
                <Explain size="xs" title="Uptime">
                  <p>
                    Uptime is the percentage of time that a publisher{"'"}s feed
                    is available and active.
                  </p>
                  {metricsTime && <EvaluationTime scoreTime={metricsTime} />}
                  <Button
                    href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking#uptime-1"
                    size="xs"
                    variant="outline"
                    target="_blank"
                  >
                    Read more
                  </Button>
                </Explain>
              </>
            ),
            alignment: "center" as const,
            allowsSorting: true,
          },
          {
            id: "deviationScore",
            width: 20,
            name: (
              <>
                DEVIATION SCORE
                <Explain size="xs" title="Deviation">
                  <p>
                    Deviation measures how close a publisher{"'"}s price is to
                    what Pyth believes to be the true market price.
                  </p>
                  {metricsTime && <EvaluationTime scoreTime={metricsTime} />}
                  <Button
                    href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking#price-deviation-1"
                    size="xs"
                    variant="outline"
                    target="_blank"
                  >
                    Read more
                  </Button>
                </Explain>
              </>
            ),
            alignment: "center" as const,
            allowsSorting: true,
          },
          {
            id: "stalledScore",
            width: 20,
            name: (
              <>
                STALLED SCORE
                <Explain size="xs" title="Stalled">
                  <p>
                    A feed is considered stalled if it is publishing the same
                    value repeatedly for the price. This score component is
                    reduced each time a feed is stalled.
                  </p>
                  {metricsTime && <EvaluationTime scoreTime={metricsTime} />}
                  <Button
                    href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking#lack-of-stalled-prices-1"
                    size="xs"
                    variant="outline"
                    target="_blank"
                  >
                    Read more
                  </Button>
                </Explain>
              </>
            ),
            alignment: "center" as const,
            allowsSorting: true,
          },
          {
            id: "score",
            name: (
              <>
                FINAL SCORE
                <Explain size="xs" title="Uptime">
                  The final score is calculated by combining the three score
                  components as follows:
                  <ul>
                    <li>
                      <b>Uptime Score</b> (40% weight)
                    </li>
                    <li>
                      <b>Deviation Score</b> (40% weight)
                    </li>
                    <li>
                      <b>Stalled Score</b> (20% weight)
                    </li>
                  </ul>
                  {metricsTime && <EvaluationTime scoreTime={metricsTime} />}
                  <Button
                    href="https://docs.pyth.network/home/oracle-integrity-staking/publisher-quality-ranking"
                    size="xs"
                    variant="outline"
                    target="_blank"
                  >
                    Read more
                  </Button>
                </Explain>
              </>
            ),
            alignment: "left" as const,
            width: SCORE_WIDTH,
            loadingSkeleton: <Score isLoading width={SCORE_WIDTH} />,
            allowsSorting: true,
          },
        ]
      : [
          { id: "slot", name: "SLOT", alignment: "left" as const, width: 40 },
          { id: "price", name: "PRICE", alignment: "left" as const, width: 40 },
          {
            id: "confidence",
            name: "CONFIDENCE INTERVAL",
            alignment: "left" as const,
            width: 50,
          },
        ];
  }
};
