import React, { useState, useCallback, useEffect, useContext, useMemo, useRef } from "react";
import equal from "fast-deep-equal";

import Paper from "@mui/material/Paper";
import Button from "@mui/material/Button";
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward";
import ArrowUpwardIcon from "@mui/icons-material/ArrowUpward";

import { Resource } from "./Resource";
import { PlayerColor } from "./PlayerColor";
import { GlobalContext } from "./GlobalContext";

import "./Trade.css";

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-non-null-assertion */

interface Resources {
  wheat: number;
  brick: number;
  wood: number;
  stone: number;
  sheep: number;
}

interface TradeItem {
  negotiator: boolean;
  self: boolean;
  name: string;
  color: string | undefined;
  valid: boolean;
  gets: any[];
  gives: any[];
  offerRejected: any;
  canSubmit?: boolean;
}

const empty: Resources = {
  wheat: 0,
  brick: 0,
  wood: 0,
  stone: 0,
  sheep: 0,
};

const Trade: React.FC = () => {
  const { sendJsonMessage, lastJsonMessage } = useContext(GlobalContext);
  const [gives, setGives] = useState<Resources>(Object.assign({}, empty));
  const [gets, setGets] = useState<Resources>(Object.assign({}, empty));
  const [turn, setTurn] = useState<any>(undefined);
  const [priv, setPriv] = useState<any>(undefined);
  const [players, setPlayers] = useState<any>(undefined);
  const [color, setColor] = useState<string | undefined>(undefined);

  const fields = useMemo(() => ["turn", "players", "private", "color"], []);

  useEffect(() => {
    if (!lastJsonMessage) {
      return;
    }
    const data = lastJsonMessage;
    switch (data.type) {
      case "game-update":
        console.log(`trade - game-update: `, data.update);
        if ("turn" in data.update && !equal(turn, data.update.turn)) {
          setTurn(data.update.turn);
        }
        if ("players" in data.update && !equal(players, data.update.players)) {
          setPlayers(data.update.players);
        }
        if ("private" in data.update && !equal(priv, data.update.private)) {
          setPriv(data.update.private);
        }
        if ("color" in data.update && color !== data.update.color) {
          setColor(data.update.color);
        }
        break;
      default:
        break;
    }
  }, [lastJsonMessage, turn, players, priv, color]);

  useEffect(() => {
    if (!sendJsonMessage) {
      return;
    }
    sendJsonMessage({
      type: "get",
      fields,
    });
  }, [sendJsonMessage, fields]);

  const transfer = useCallback(
    (type: string, direction: string) => {
      if (direction === "give") {
        /* give clicked */
        if (gets[type as keyof Resources]) {
          gets[type as keyof Resources]--;
          gives[type as keyof Resources] = 0;
        } else {
          if (gives[type as keyof Resources] < priv[type]) {
            gives[type as keyof Resources]++;
          }
          gets[type as keyof Resources] = 0;
        }
      } else if (direction === "get") {
        /* get clicked */
        if (gives[type as keyof Resources]) {
          gives[type as keyof Resources]--;
          gets[type as keyof Resources] = 0;
        } else {
          if (gets[type as keyof Resources] < 15) {
            gets[type as keyof Resources]++;
          }
          gives[type as keyof Resources] = 0;
        }
      }

      setGets({ ...gets });
      setGives({ ...gives });
    },
    [setGets, setGives, gets, gives, priv]
  );

  const createTransfer = useCallback(
    (resource: string) => {
      return (
        <div key={resource} className="Transfer">
          <Resource
            onClick={() => transfer(resource, "get")}
            label={true}
            type={resource}
            disabled
            count={gets[resource as keyof Resources]}
          />
          <div className="Direction">
            {gets[resource as keyof Resources] === gives[resource as keyof Resources] ? (
              ""
            ) : gets[resource as keyof Resources] > gives[resource as keyof Resources] ? (
              <ArrowDownwardIcon />
            ) : (
              <ArrowUpwardIcon />
            )}
          </div>
          <Resource
            onClick={() => transfer(resource, "give")}
            label={true}
            type={resource}
            disabled
            available={priv ? priv[resource] - gives[resource as keyof Resources] : undefined}
            count={gives[resource as keyof Resources]}
          />
        </div>
      );
    },
    [gives, gets, transfer, priv]
  );

  const sendTrade = useCallback(
    (action: string, offer: any) => {
      if (sendJsonMessage) {
        sendJsonMessage({
          type: "trade",
          action,
          offer,
        });
      }
    },
    [sendJsonMessage]
  );

  useEffect(() => {
    if (priv && priv.gives) {
      const _gives: Partial<Resources> = {};
      priv.gives.forEach((give: any) => (_gives[give.type as keyof Resources] = give.count));
      setGives(Object.assign({}, empty, _gives));
    }
    if (priv && priv.gets) {
      const _gets: Partial<Resources> = {};
      priv.gets.forEach((get: any) => (_gets[get.type as keyof Resources] = get.count));
      setGets(Object.assign({}, empty, _gets));
    }
  }, [setGets, setGives, priv]);

  const agreeClicked = useCallback(
    (offer: any) => {
      const trade = {
        gives: offer.gets.slice(),
        gets: offer.gives.slice(),
      };
      const _gives: Partial<Resources> = {},
        _gets: Partial<Resources> = {};
      console.log(gives, gets);
      trade.gives.forEach((give: any) => (_gives[give.type as keyof Resources] = give.count));
      trade.gets.forEach((get: any) => (_gets[get.type as keyof Resources] = get.count));
      sendTrade("offer", trade);
      console.log(_gives, _gets);
      setGives(Object.assign({}, empty, _gives));
      setGets(Object.assign({}, empty, _gets));
    },
    [setGives, setGets, gives, gets, sendTrade]
  );

  if (!priv || !turn || !turn.actions || turn.actions.indexOf("trade") === -1) {
    return <></>;
  }

  const transfers = ["brick", "wood", "wheat", "sheep", "stone"].map((resource) => {
    return createTransfer(resource);
  });

  priv.offerRejected = priv.offerRejected ? priv.offerRejected : {};

  const canMeetOffer = (player: any, offer: any) => {
    if (offer.gets.length === 0 || offer.gives.length === 0) {
      return false;
    }
    for (let i = 0; i < offer.gets.length; i++) {
      const get = offer.gets[i];
      if (offer.name === "The bank") {
        const _gives: any[] = [],
          _gets: any[] = [];
        for (const type in gives) {
          if (gives[type as keyof Resources] > 0) {
            _gives.push({ type, count: gives[type as keyof Resources] });
          }
        }
        for (const type in gets) {
          if (gets[type as keyof Resources] > 0) {
            _gets.push({ type, count: gets[type as keyof Resources] });
          }
        }

        if (_gives.length !== 1 || _gets.length !== 1) {
          return false;
        }

        if (_gives[0].count < get.count) {
          return false;
        }

        if (get.type !== "bank") {
          if (gives[get.type as keyof Resources] < get.count) {
            return false;
          }
        }

        if (_gets[0].count !== 1) {
          return false;
        }
      } else if (player[get.type] < get.count) {
        console.log(`cannot meet count`);
        return false;
      }
    }
    return true;
  };

  const isCompatibleOffer = (player: any, offer: any) => {
    let valid =
      player.gets &&
      player.gives &&
      offer.gets &&
      offer.gives &&
      player.gets.length === offer.gives.length &&
      player.gives.length === offer.gets.length;

    if (!valid) {
      return false;
    }

    player.gets.forEach((get: any) => {
      if (!valid) {
        return;
      }
      valid =
        offer.gives.find((item: any) => (item.type === get.type || item.type === "*") && item.count === get.count) !==
        undefined;
    });

    if (valid)
      player.gives.forEach((give: any) => {
        if (!valid) {
          return;
        }
        valid =
          offer.gets.find(
            (item: any) => (item.type === give.type || item.type === "bank") && item.count === give.count
          ) !== undefined;
      });
    return valid;
  };

  const isTurn = turn && turn.color === color ? true : false;

  const offerClicked = () => {
    const trade = {
      gives: [] as any[],
      gets: [] as any[],
    };
    for (const key in gives) {
      if (gives[key as keyof Resources] !== 0) {
        trade.gives.push({ type: key, count: gives[key as keyof Resources] });
      }
    }
    for (const key in gets) {
      if (gets[key as keyof Resources] !== 0) {
        trade.gets.push({ type: key, count: gets[key as keyof Resources] });
      }
    }
    sendTrade("offer", trade);
  };

  const cancelOffer = (offer: any) => {
    sendTrade("cancel", offer);
  };

  const acceptClicked = (offer: any) => {
    if (offer.name === "The bank") {
      sendTrade("accept", Object.assign({}, { name: offer.name, gives: trade.gets, gets: trade.gives }));
    } else if (offer.self) {
      sendTrade("accept", offer);
    } else {
      sendTrade("accept", Object.assign({}, offer, { gives: offer.gets, gets: offer.gives }));
    }
  };

  // cancelClicked was unused; use cancelOffer when needed. Keep function removed.

  /* Player has rejected the active player's bid or active player rejected
   * the other player's bid */
  const rejectClicked = (trade: any) => {
    sendTrade("reject", trade);
  };

  /* Create list of active trades */
  const activeTrades: any[] = [];
  for (const colorKey in players) {
    const item = players[colorKey],
      name = item.name;
    item.offerRejected = item.offerRejected ? item.offerRejected : {};
    if (item.status !== "Active") {
      continue;
    }
    /* Only list players with an offer, unless it is the active player (see
     * that you haven't submitted an offer) or the current turn player,
     * or the player explicitly rejected the player's offer */
    if (
      turn.name !== name &&
      priv.name !== name &&
      !(colorKey in priv.offerRejected) &&
      (!item.gets || item.gets.length === 0 || !item.gives || item.gives.length === 0)
    ) {
      continue;
    }

    const tmp: TradeItem = {
      negotiator: turn.name === name,
      self: priv.name === name,
      name: name,
      color: colorKey,
      valid: false,
      gets: item.gets ? item.gets : [],
      gives: item.gives ? item.gives : [],
      offerRejected: item.offerRejected,
    };

    tmp.canSubmit = !!(tmp.gets.length && tmp.gives.length);

    activeTrades.push(tmp);
  }

  activeTrades.sort((A: any, B: any) => {
    if (A.negotiator) {
      return -1;
    }
    if (B.negotiator) {
      return +1;
    }
    if (A.self) {
      return -1;
    }
    if (B.self) {
      return +1;
    }
    return A.name.localeCompare(B.name);
  });

  const trade = { gives: [] as any[], gets: [] as any[] };
  for (const type in gives) {
    if (gives[type as keyof Resources]) {
      trade.gets.push({ type, count: gives[type as keyof Resources] });
    }
  }
  for (const type in gets) {
    if (gets[type as keyof Resources]) {
      trade.gives.push({ type, count: gets[type as keyof Resources] });
    }
  }

  const isOfferSubmitted = isCompatibleOffer(priv, trade),
    isNegiatorSubmitted = turn && turn.offer && isCompatibleOffer(priv, turn.offer),
    isOfferValid = trade.gives.length && trade.gets.length ? true : false;

  if (isTurn && priv && priv.banks) {
    priv.banks.forEach((bank: string) => {
      const count = bank === "bank" ? 3 : 2;
      activeTrades.push({
        name: `The bank`,
        color: undefined,
        gives: [{ count: 1, type: "*" }],
        gets: [{ count: count, type: bank }],
        valid: false,
        offerRejected: {},
      });
    });

    activeTrades.push({
      name: `The bank`,
      color: undefined,
      gives: [{ count: 1, type: "*" }],
      gets: [{ count: 4, type: "bank" }],
      valid: false,
      offerRejected: {},
    });
  }

  if (isTurn) {
    activeTrades.forEach((offer: any) => {
      if (offer.name === "The bank") {
        /* offer has to be the second parameter for the bank to match */
        offer.valid = isCompatibleOffer({ gives: trade.gets, gets: trade.gives }, offer);
      } else {
        offer.valid = !(turn.color in offer.offerRejected) && canMeetOffer(priv, offer);
      }
    });
  } else {
    const found = activeTrades.find((item: any) => item.name === turn.name);
    if (found) {
      found.valid = !(color! in found.offerRejected) && canMeetOffer(priv, found);
    }
  }

  const tradeElements = activeTrades.map((item: any, index: number) => {
    const youRejectedOffer = color! in item.offerRejected;
    let youWereRejected;
    if (isTurn) {
      youWereRejected = item.color && item.color in priv.offerRejected;
    } else {
      youWereRejected = Object.getOwnPropertyNames(priv.offerRejected).length !== 0;
    }

    const isNewOffer = item.self && !isOfferSubmitted;

    let isSameOffer;
    const isBank = item.name === "The bank";

    if (isTurn) {
      isSameOffer = isCompatibleOffer(trade, { gets: item.gives, gives: item.gets });
    } else {
      isSameOffer = turn.offer && isCompatibleOffer(priv, turn.offer);
    }

    let source;
    if (item.self) {
      /* Order direction is reversed for self */
      source = {
        name: item.name,
        color: item.color,
        gets: trade.gives,
        gives: trade.gets,
      };
    } else {
      source = item;
    }
    const _gets = source.gets.length
      ? source.gets.map((get: any, index: number) => {
          if (get.type === "bank") {
            return (
              <span key={`get-bank-${index}`}>
                <b>{get.count}</b> of any resource{" "}
              </span>
            );
          }
          return <Resource key={`get-${get.type}-${index}`} disabled label type={get.type} count={get.count} />;
        })
      : "nothing";
    const _gives = source.gives.length
      ? source.gives.map((give: any, index: number) => {
          if (give.type === "*") {
            return (
              <span key={`give-bank-${index}`}>
                <b>1</b> of any resource{" "}
              </span>
            );
          }
          return <Resource key={`give-${give.type}-${index}`} disabled label type={give.type} count={give.count} />;
        })
      : "nothing";

    return (
      <div className="TradeLine" key={`player-${item.name}-${index}`}>
        <PlayerColor color={item.color} />
        <div className="TradeText">
          {item.self && (
            <>
              {(_gets !== "nothing" || _gives !== "nothing") && (
                <span>
                  You want {_gets} and will give {_gives}.
                </span>
              )}

              {youWereRejected && !isNewOffer && <span>{turn.name} rejected your offer.</span>}

              {!youWereRejected && _gets === "nothing" && _gives === "nothing" && (
                <span>You have not made a trade offer.</span>
              )}

              {!isTurn &&
                isSameOffer &&
                !youWereRejected &&
                isOfferValid &&
                _gets !== "nothing" &&
                _gives !== "nothing" && (
                  <span style={{ fontWeight: "bold" }}>Your submitted offer agrees with {turn.name}&apos;s terms.</span>
                )}
            </>
          )}

          {!item.self && (
            <>
              {(!isTurn || !isSameOffer || isBank) &&
                !youRejectedOffer &&
                _gets !== "nothing" &&
                _gives !== "nothing" && (
                  <span>
                    {item.name} wants {_gets} and will give {_gives}.
                  </span>
                )}

              {!isBank && (
                <>
                  {isTurn &&
                    !isSameOffer &&
                    isOfferValid &&
                    !youRejectedOffer &&
                    _gets !== "nothing" &&
                    _gives !== "nothing" && <span style={{ fontWeight: "bold" }}>This is a counter offer.</span>}

                  {isTurn && isSameOffer && !youRejectedOffer && _gets !== "nothing" && _gives !== "nothing" && (
                    <span>{item.name} will meet your terms.</span>
                  )}

                  {(!isTurn || !youWereRejected) && (_gets === "nothing" || _gives === "nothing") && (
                    <span>{item.name} has not submitted a trade offer.</span>
                  )}

                  {youRejectedOffer && <span>You rejected {item.name}&apos;s offer.</span>}

                  {isTurn && youWereRejected && <span>{item.name} rejected your offer.</span>}
                </>
              )}
            </>
          )}
        </div>
        <div className="TradeActions">
          {!item.self && isTurn && (
            <Button disabled={!item.valid} onClick={() => acceptClicked(item)}>
              accept
            </Button>
          )}

          {!isTurn && item.color === turn.color && (
            <Button disabled={!item.valid || isNegiatorSubmitted} onClick={() => agreeClicked(item)}>
              agree
            </Button>
          )}

          {item.name !== "The bank" && !item.self && (isTurn || item.name === turn.name) && (
            <Button
              disabled={!item.gets.length || !item.gives.length || youRejectedOffer}
              onClick={() => rejectClicked(item)}
            >
              reject
            </Button>
          )}

          {item.self && (
            <Button disabled={isOfferSubmitted || !isOfferValid} onClick={offerClicked}>
              Offer
            </Button>
          )}

          {item.self && (
            <Button disabled onClick={() => cancelOffer(item)}>
              cancel
            </Button>
          )}
        </div>
      </div>
    );
  });

  return (
    <Paper className="Trade">
      <div className="PlayerList">{tradeElements}</div>
      {priv.resources === 0 && (
        <div>
          <b>You have no resources to participate in this trade.</b>
        </div>
      )}
      {priv.resources !== 0 && (
        <div className="Transfers">
          <div className="GiveGet">
            <div>Get</div>
            <div>Give</div>
            <div>Have</div>
          </div>
          {transfers}
        </div>
      )}
    </Paper>
  );
};

export { Trade };
