import React, {
  createContext,
  ReactNode,
  useCallback,
  useContext,
  useEffect,
  useRef,
  useState,
} from "react";
import { useAccount, useReadContract, useWriteContract } from "wagmi";
import contractAbi from "../../../out/RaidGeld.sol/RaidGeld.json";
import { Hash, parseEther } from "viem";
import contracts from "../../contract_address";
import WaitingForTxModal from "../components/WaitingForTxModal";
import BossOutcomeModal from "../components/BossOutcomeModal";
import styles from "../styles/Background.module.css";
import { coinSound } from "../utils/soundsEffect";

const { contractAddress, daoTokenAddress } = contracts;
const abi = contractAbi.abi;

export type UnitType = 0 | 1 | 2 | 3;
export type BossLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6;

export interface Player {
  created_at: bigint;
  last_raided_at: bigint;
  total_minted: bigint;
  total_rewards: bigint;
  n_runs: number;
  prestige_level: number;
  is_registered: boolean;
  has_active_session: boolean;
}
export interface Army {
  anointed: { level: number };
  apprentice: { level: number };
  champion: { level: number };
  moloch_denier: { level: number };
  profit_per_second: bigint;
}
export interface Boss {
  level: BossLevel;
  variants: [
    BossLevel,
    BossLevel,
    BossLevel,
    BossLevel,
    BossLevel,
    BossLevel,
    BossLevel
  ];
}

export interface LastBossResult {
  level: BossLevel;
  variant: BossLevel;
  battled_at: bigint;
  reward: bigint;
  prestigeGained: boolean;
}

export interface PlayerContextType {
  isRegistered: boolean;
  player: null | Player;
  army: null | Army;
  boss: null | Boss;
  lastBossResult: null | LastBossResult;
  balance: bigint;
  register: (arg: "ETH" | "RGCVII") => void;
  raid: () => void;
  battleWithBoss: () => void;
  addUnit: (unit: UnitType, amount?: number) => void;
}

const PlayerContext = createContext<PlayerContextType>({
  isRegistered: false,
  player: null,
  army: null,
  boss: null,
  lastBossResult: null,
  balance: BigInt(0),
  register: () => {},
  raid: () => {},
  battleWithBoss: () => {},
  addUnit: () => {},
});

const PlayerProvider = ({ children }: { children: ReactNode }) => {
  const { address, isConnected } = useAccount();
  const { writeContract, error } = useWriteContract();
  const [[txHash, callbackFn], setHashAndCallback] = useState<
    [Hash | null, () => void]
  >([null, () => {}]);
  const [bossBattledModalOpen, setBossBattlesModalOpen] = useState(false);
  const hasFetchedLastBossFirstTime = useRef(false);

  useEffect(() => {
    console.warn(error);
  }, [error]);

  const resetHashAndCallback = useCallback(() => {
    setHashAndCallback([null, () => {}]);
  }, []);

  const { data: isRegistered } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "isRegistered",
    args: [address],
    query: {
      enabled: isConnected,
      refetchInterval: 10000,
    },
  });

  const { data: balance } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "balanceOf",
    args: [address],
    query: {
      refetchInterval: 10000,
      enabled: isConnected,
    },
  });

  const { data: player } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "getPlayer",
    args: [address],
    query: {
      enabled: isConnected,
      refetchInterval: 10000,
    },
  });

  const { data: army } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "getArmy",
    args: [address],
    query: {
      enabled: isConnected,
      refetchInterval: 10000,
    },
  });

  const { data: boss } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "getBoss",
    args: [address],
    query: {
      enabled: isConnected,
      refetchInterval: 10000,
    },
  });

  const { data: lastBossResult } = useReadContract({
    address: contractAddress,
    abi,
    functionName: "getLastBossResult",
    args: [address],
    query: {
      enabled: isConnected,
      refetchInterval: 10000,
    },
  });

  const register = useCallback(
    (arg: "RGCVII" | "ETH") => {
      if (arg === "ETH") {
        writeContract(
          {
            abi,
            address: contractAddress,
            functionName: "register_eth",
            value: parseEther("0.00045"),
          },
          {
            onSuccess: (hash) => {
              setHashAndCallback([hash, resetHashAndCallback]);
            },
            onError: () => resetHashAndCallback(),
          }
        );
      } else if (arg === "RGCVII") {
        writeContract(
          {
            abi,
            address: daoTokenAddress,
            functionName: "approve",
            args: [contractAddress, parseEther("400")],
          },
          {
            onSuccess: (hash) => {
              setHashAndCallback([
                hash,
                () =>
                  writeContract(
                    {
                      abi,
                      address: contractAddress,
                      functionName: "register_dao",
                    },
                    {
                      onSuccess: (hash) => {
                        setHashAndCallback([hash, resetHashAndCallback]);
                      },
                      onError: () => resetHashAndCallback(),
                    }
                  ),
              ]);
            },
            onError: () => resetHashAndCallback(),
          }
        );
      }
    },
    [writeContract, resetHashAndCallback]
  );

  const raid = useCallback(() => {
    writeContract(
      {
        abi,
        address: contractAddress,
        functionName: "raid",
      },
      {
        onSuccess: (hash) => {
          setHashAndCallback([hash, resetHashAndCallback]);
        },
        onError: () => resetHashAndCallback(),
      }
    );
  }, [writeContract, resetHashAndCallback]);

  const addUnit = useCallback(
    (unit: UnitType) => {
      writeContract(
        {
          abi,
          address: contractAddress,
          functionName: "addUnit",
          args: [unit, 1],
        },
        {
          onSuccess: () => {
            coinSound();
          },
          onError: () => resetHashAndCallback(),
        }
      );
    },
    [writeContract, resetHashAndCallback]
  );

  const battleWithBoss = useCallback(() => {
    writeContract(
      {
        abi,
        address: contractAddress,
        functionName: "battle_with_boss",
      },
      {
        onSuccess: (hash) => {
          setHashAndCallback([hash, () => resetHashAndCallback()]);
        },
        onError: () => resetHashAndCallback(),
      }
    );
  }, [writeContract, resetHashAndCallback]);

  useEffect(() => {
    if (lastBossResult != null) {
      if (hasFetchedLastBossFirstTime.current) {
        setBossBattlesModalOpen(true);
      } else {
        hasFetchedLastBossFirstTime.current = true;
      }
    }
  }, [lastBossResult]);

  return (
    <PlayerContext.Provider
      value={{
        isRegistered: isRegistered as boolean,
        player: player as Player,
        army: army as Army,
        boss: boss as Boss,
        balance: balance as bigint,
        lastBossResult: lastBossResult as LastBossResult,
        register,
        raid,
        addUnit,
        battleWithBoss,
      }}
    >
      {children}
      <div
        className={`${
          txHash || bossBattledModalOpen ? styles.leaderboardOverlay : ""
        }`}
      >
        {txHash && <WaitingForTxModal hash={txHash} callbackFn={callbackFn} />}
        {bossBattledModalOpen && (
          <BossOutcomeModal setIsOpen={setBossBattlesModalOpen} />
        )}
      </div>
    </PlayerContext.Provider>
  );
};

export const usePlayer = () => {
  return useContext(PlayerContext);
};

export default PlayerProvider;
