import { ethers } from "ethers";
import { EventEmitter } from "events";
import { 
  StakeInfo, 
  StakingContractConfig, 
  StakingOperationResult, 
  StakingStats, 
  StakingViewResult, 
  UserStakeInfo 
} from "./types";
import { STAKING_CONTRACT_ABI, STAKING_CONTRACT_ADDRESS } from "./constants";

export class StakingContract extends EventEmitter {
  private contract: ethers.Contract | null = null;
  private provider: ethers.Provider | null = null;
  private signer: ethers.Signer | null = null;
  private address: string;

  constructor(config: StakingContractConfig = {}) {
    super();
    this.address = config.contractAddress || STAKING_CONTRACT_ADDRESS;
  }


  public async initialize(provider?: ethers.Provider, signer?: ethers.Signer): Promise<void> {
    if (!provider) {
      if (!this.provider) {
        throw new Error("Provider is required for initialization");
      }
    } else {
      this.provider = provider;
    }

    if (signer) {
      this.signer = signer;
    } else if (this.provider) {
      // If no signer provided but we have a provider, create a read-only contract
      this.contract = new ethers.Contract(
        this.address,
        STAKING_CONTRACT_ABI,
        this.provider
      );
      return;
    }

    if (!this.signer) {
      throw new Error("Signer is required for write operations");
    }

    this.contract = new ethers.Contract(
      this.address,
      STAKING_CONTRACT_ABI,
      this.signer
    );
  }

  public async cleanup(): Promise<void> {
    this.contract = null;
    this.provider = null;
    this.signer = null;
  }

  public async stake(recipient: string, amount: bigint): Promise<StakingOperationResult> {
    try {
      if (!this.contract || !this.signer) {
        throw new Error("Contract not initialized with signer");
      }

      console.log("Staking with params:", {
        recipient,
        amount: amount.toString(),
        contractAddress: this.address
      });

      // Debug the exact data being sent
      const data = this.contract.interface.encodeFunctionData("stake", [recipient, amount]);
      console.log("Amount in BigInt:", amount);
      console.log("Encoded transaction data:", data);

      const tx = await this.contract.stake(recipient, amount, {
        gasLimit: 300000 // Reduced gas limit to a more reasonable value
      });
      const receipt = await tx.wait();

      return {
        success: true,
        transactionHash: receipt.hash
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async initiateUnstake(amount: bigint): Promise<StakingOperationResult> {
    try {
      if (!this.contract || !this.signer) {
        throw new Error("Contract not initialized with signer");
      }

      const tx = await this.contract.initiateUnstake(amount);
      const receipt = await tx.wait();

      return {
        success: true,
        transactionHash: receipt.hash
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async finalizeUnstake(): Promise<StakingOperationResult> {
    try {
      if (!this.contract || !this.signer) {
        throw new Error("Contract not initialized with signer");
      }

      const tx = await this.contract.finalizeUnstake();
      const receipt = await tx.wait();

      return {
        success: true,
        transactionHash: receipt.hash
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async claim(): Promise<StakingOperationResult> {
    try {
      if (!this.contract || !this.signer) {
        throw new Error("Contract not initialized with signer");
      }

      const tx = await this.contract.claim();
      const receipt = await tx.wait();

      return {
        success: true,
        transactionHash: receipt.hash
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async getPendingRewards(userAddress: string): Promise<StakingViewResult<bigint>> {
    try {
      if (!this.contract) {
        throw new Error("Contract not initialized");
      }

      const pendingRewards = await this.contract.pendingRewards(userAddress);

      return {
        success: true,
        data: pendingRewards
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async getVenicePercentage(): Promise<StakingViewResult<bigint>> {
    try {
      if (!this.contract) {
        throw new Error("Contract not initialized");
      }

      const percentage = await this.contract.getVenicePercentage();

      return {
        success: true,
        data: percentage
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async getStakeInfo(userAddress: string): Promise<StakingViewResult<StakeInfo>> {
    try {
      if (!this.contract) {
        throw new Error("Contract not initialized");
      }

      const stakeInfo = await this.contract.stakes(userAddress);

      return {
        success: true,
        data: {
          rewardDebt: stakeInfo[0],
          cooldownEnd: stakeInfo[1],
          cooldownAmount: stakeInfo[2]
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async getUserStakeInfo(userAddress: string): Promise<StakingViewResult<UserStakeInfo>> {
    try {
      if (!this.contract) {
        throw new Error("Contract not initialized");
      }

      const [stakeInfoResult, pendingRewardsResult] = await Promise.all([
        this.getStakeInfo(userAddress),
        this.getPendingRewards(userAddress)
      ]);

      if (!stakeInfoResult.success || !pendingRewardsResult.success) {
        throw new Error("Failed to fetch user stake info");
      }

      // Get the user's staked balance
      const stakeAmount = await this.contract.balanceOf(userAddress);

      return {
        success: true,
        data: {
          stakeAmount,
          pendingRewards: pendingRewardsResult.data!,
          stakeInfo: stakeInfoResult.data!
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

  public async getStakingStats(): Promise<StakingViewResult<StakingStats>> {
    try {
      if (!this.contract) {
        throw new Error("Contract not initialized");
      }

      const [
        totalSupply,
        emissionRate,
        cooldownDuration,
        venicePercentage,
        accRewardPerShare,
        lastRewardTimestamp
      ] = await Promise.all([
        this.contract.totalSupply(),
        this.contract.emissionRatePerSecond(),
        this.contract.cooldownDuration(),
        this.contract.getVenicePercentage(),
        this.contract.accRewardPerShare(),
        this.contract.lastRewardTimestamp()
      ]);

      return {
        success: true,
        data: {
          totalStaked: totalSupply,
          emissionRate,
          cooldownDuration,
          venicePercentage,
          accRewardPerShare,
          lastRewardTimestamp
        }
      };
    } catch (error) {
      return {
        success: false,
        error: error instanceof Error ? error.message : String(error)
      };
    }
  }

} 