import React from "react";
import {
  PublicKey,
  ConfirmedSignatureInfo,
  ParsedInstruction,
} from "@solana/web3.js";
import { FetchStatus } from "providers/accounts";
import {
  useAccountHistories,
  useFetchAccountHistory,
} from "providers/accounts/history";
import {
  useAccountOwnedTokens,
  TokenAccountData,
} from "providers/accounts/tokens";
import { ErrorCard } from "components/common/ErrorCard";
import { LoadingCard } from "components/common/LoadingCard";
import { Signature } from "components/common/Signature";
import { Address } from "components/common/Address";
import { useTransactionDetails } from "providers/transactions";
import { useFetchTransactionDetails } from "providers/transactions/details";
import { coerce } from "superstruct";
import { ParsedInfo } from "validators";
import {
  TokenInstructionType,
  IX_TITLES,
} from "components/instruction/token/types";

export function TokenHistoryCard({ pubkey }: { pubkey: PublicKey }) {
  const address = pubkey.toBase58();
  const ownedTokens = useAccountOwnedTokens(address);

  if (ownedTokens === undefined) {
    return null;
  }

  const { tokens } = ownedTokens;
  if (tokens === undefined || tokens.length === 0) return null;

  return <TokenHistoryTable tokens={tokens} />;
}

function TokenHistoryTable({ tokens }: { tokens: TokenAccountData[] }) {
  const accountHistories = useAccountHistories();
  const fetchAccountHistory = useFetchAccountHistory();

  const fetchHistories = (refresh?: boolean) => {
    tokens.forEach((token) => {
      fetchAccountHistory(token.pubkey, refresh);
    });
  };

  // Fetch histories on load
  React.useEffect(() => {
    tokens.forEach((token) => {
      const address = token.pubkey.toBase58();
      if (!accountHistories[address]) {
        fetchAccountHistory(token.pubkey, true);
      }
    });
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const fetchedFullHistory = tokens.every((token) => {
    const history = accountHistories[token.pubkey.toBase58()];
    return history && history.foundOldest === true;
  });

  const fetching = tokens.some((token) => {
    const history = accountHistories[token.pubkey.toBase58()];
    return history && history.status === FetchStatus.Fetching;
  });

  const failed = tokens.some((token) => {
    const history = accountHistories[token.pubkey.toBase58()];
    return history && history.status === FetchStatus.FetchFailed;
  });

  const mintAndTxs = tokens
    .map((token) => ({
      mint: token.mint,
      history: accountHistories[token.pubkey.toBase58()],
    }))
    .filter(({ history }) => {
      return (
        history !== undefined && history.fetched && history.fetched.length > 0
      );
    })
    .flatMap(({ mint, history }) =>
      (history.fetched as ConfirmedSignatureInfo[]).map((tx) => ({ mint, tx }))
    );

  if (mintAndTxs.length === 0) {
    if (fetching) {
      return <LoadingCard message="Loading history" />;
    } else if (failed) {
      return (
        <ErrorCard
          retry={() => fetchHistories(true)}
          text="Failed to fetch transaction history"
        />
      );
    }
    return (
      <ErrorCard
        retry={() => fetchHistories(true)}
        retryText="Try again"
        text="No transaction history found"
      />
    );
  }

  mintAndTxs.sort((a, b) => {
    if (a.tx.slot > b.tx.slot) return -1;
    if (a.tx.slot < b.tx.slot) return 1;
    return 0;
  });

  return (
    <div className="card">
      <div className="card-header align-items-center">
        <h3 className="card-header-title">Token History</h3>
        <button
          className="btn btn-white btn-sm"
          disabled={fetching}
          onClick={() => fetchHistories(true)}
        >
          {fetching ? (
            <>
              <span className="spinner-grow spinner-grow-sm mr-2"></span>
              Loading
            </>
          ) : (
            <>
              <span className="fe fe-refresh-cw mr-2"></span>
              Refresh
            </>
          )}
        </button>
      </div>

      <div className="table-responsive mb-0">
        <table className="table table-sm table-nowrap card-table">
          <thead>
            <tr>
              <th className="text-muted w-1">Slot</th>
              <th className="text-muted">Result</th>
              <th className="text-muted">Token</th>
              <th className="text-muted">Instruction Type</th>
              <th className="text-muted">Transaction Signature</th>
            </tr>
          </thead>
          <tbody className="list">
            {mintAndTxs.map(({ mint, tx }) => (
              <TokenTransactionRow key={tx.signature} mint={mint} tx={tx} />
            ))}
          </tbody>
        </table>
      </div>

      <div className="card-footer">
        {fetchedFullHistory ? (
          <div className="text-muted text-center">Fetched full history</div>
        ) : (
          <button
            className="btn btn-primary w-100"
            onClick={() => fetchHistories()}
            disabled={fetching}
          >
            {fetching ? (
              <>
                <span className="spinner-grow spinner-grow-sm mr-2"></span>
                Loading
              </>
            ) : (
              "Load More"
            )}
          </button>
        )}
      </div>
    </div>
  );
}

function TokenTransactionRow({
  mint,
  tx,
}: {
  mint: PublicKey;
  tx: ConfirmedSignatureInfo;
}) {
  const details = useTransactionDetails(tx.signature);
  const fetchDetails = useFetchTransactionDetails();

  // Fetch details on load
  React.useEffect(() => {
    if (!details) fetchDetails(tx.signature);
  }, []); // eslint-disable-line react-hooks/exhaustive-deps

  const instructions = details?.transaction?.transaction.message.instructions;
  if (instructions) {
    const tokenInstructions = instructions.filter(
      (ix) => "parsed" in ix && ix.program === "spl-token"
    ) as ParsedInstruction[];
    if (tokenInstructions.length > 0) {
      return (
        <>
          {tokenInstructions.map((ix, index) => {
            const parsed = coerce(ix.parsed, ParsedInfo);
            const { type: rawType } = parsed;
            const type = coerce(rawType, TokenInstructionType);
            const typeName = IX_TITLES[type];

            let statusText;
            let statusClass;
            if (tx.err) {
              statusClass = "warning";
              statusText = "Failed";
            } else {
              statusClass = "success";
              statusText = "Success";
            }

            return (
              <tr key={index}>
                <td className="w-1">{tx.slot}</td>

                <td>
                  <span className={`badge badge-soft-${statusClass}`}>
                    {statusText}
                  </span>
                </td>

                <td>
                  <Address pubkey={mint} link />
                </td>

                <td>{typeName}</td>

                <td>
                  <Signature signature={tx.signature} link />
                </td>
              </tr>
            );
          })}
        </>
      );
    }
  }

  let statusText;
  let statusClass;
  if (tx.err) {
    statusClass = "warning";
    statusText = "Failed";
  } else {
    statusClass = "success";
    statusText = "Success";
  }

  return (
    <tr key={tx.signature}>
      <td className="w-1">{tx.slot}</td>

      <td>
        <span className={`badge badge-soft-${statusClass}`}>{statusText}</span>
      </td>

      <td>
        <span className="spinner-grow spinner-grow-sm mr-2"></span>
        Loading
      </td>

      <td>
        <Address pubkey={mint} link />
      </td>

      <td>
        <Signature signature={tx.signature} link />
      </td>
    </tr>
  );
}
