import {Injectable} from '@angular/core';
import {Contract} from 'web3-eth-contract';
import {Subject} from 'rxjs';
import {Web3Service} from '../../../integrations/services/web3/web3.service';
import {UtilsService} from './utils.service';
import {BLOCKS_PER_DAY, DIVIDER_FOR_BALANCE, PID, STAKING_CONTRACT_ADDRESS_MAIN_NET, TOKEN_DECIMALS, WEB3, XFT_APY_CONST} from '../../../integrations/dictionaries/meta-mask.dictionary';
import {ONSEN_CONTRACT_ADDRESS_MAIN_NET} from '../../../integrations/dictionaries/meta-mask.dictionary';
import sushiData from '@sushiswap/sushi-data';
import {AngularWeb3RecentTransactionsService} from 'angular-web3-components';

@Injectable({
    providedIn: 'root'
})
export class StakeSlpService {
    private slpBalance: string;
    public slpBalanceChange = new Subject<string>();

    private staked: string;
    public stakedChange = new Subject<string>();

    private slpAllowance: string;
    public slpAllowanceChange = new Subject<string>();

    private rewardsXFT: number;
    public rewardsXFTChange = new Subject<number>();

    private rewardsSushi: number;
    public rewardsSushiChange = new Subject<number>();

    private apySushi: any;
    public apySushiChange = new Subject<number>();

    private apyXFT: any;
    public apyXFTChange = new Subject<number>();

    constructor(
        private web3Service: Web3Service,
        private utilsService: UtilsService,
        private recentTransactionsService: AngularWeb3RecentTransactionsService,
    ) {}

    public getSLPBalance(accountAddress: string): void {
        if (this.slpBalance) {
            this.slpBalanceChange.next(this.slpBalance);
        } else {
            this.fetchSLPBalance(accountAddress);
        }
    }

    public fetchSLPBalance(accountAddress: string): void {
        this.web3Service.getSlpContract().then((contract: Contract) => {
            contract.methods.balanceOf(accountAddress).call({from: accountAddress}).then((res: string) => {
                this.slpBalance = res;
                this.slpBalanceChange.next(this.slpBalance);
            });
        });
    }

    public  getStaked(accountAddress: string): void {
        if (this.staked) {
            this.stakedChange.next(this.staked);
        } else {
            this.fetchStaked(accountAddress);
        }
    }

    public  fetchStaked(accountAddress: string): void {
        this.web3Service.getStakingContract().then((contract: Contract) => {
            contract.methods.balanceOf(accountAddress).call({from: accountAddress}).then((res: string) => {
                this.staked = res;
                this.stakedChange.next(this.staked);
            });
        });
    }

    public  getSLPAllowance(accountAddress: string): void {
        if (this.slpAllowance) {
            this.slpAllowanceChange.next(this.slpAllowance);
        } else {
            this.fetchSLPAllowance(accountAddress);
        }
    }

    public fetchSLPAllowance(accountAddress: string): void {
        this.web3Service.getSlpContract().then((contract: Contract) => {
            contract.methods.allowance(accountAddress, STAKING_CONTRACT_ADDRESS_MAIN_NET).call({from: accountAddress}).then((res: string) => {
                this.slpAllowance = res;
                this.slpAllowanceChange.next(this.slpAllowance);
            });
        });
    }

    public approveNewAllowance(amount: string, accountAddress: string): Promise<any> {

        return this.web3Service.getSlpContract().then((contract: Contract) => {
            return contract.methods.approve(
                STAKING_CONTRACT_ADDRESS_MAIN_NET,
                amount
            ).send({from: accountAddress}, (err, hash) => {
                if (!err) {
                    this.recentTransactionsService.saveTransaction('Approve new allowance for stake SLP', hash);
                }
            });
        });
    }

    public stake(amount: string, accountAddress: string): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            this.web3Service.getStakingContract().then((contract: Contract) => {
                contract.methods.stake(amount).send({from: accountAddress}, (err, hash) => {
                    if (!err) {
                        this.recentTransactionsService.saveTransaction('Stake SLP', hash);
                        resolve(hash);
                    } else {
                        reject(err);
                    }
                });
            });
        });
    }

    public unStake(amount: string, accountAddress: string): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            this.web3Service.getStakingContract().then((contract: Contract) => {
                contract.methods.withdraw(amount).send({from: accountAddress}, (err, hash) => {
                    if (!err) {
                        this.recentTransactionsService.saveTransaction('Withdraw SLP', hash);
                        resolve(hash);
                    } else {
                        reject(err);
                    }
                });
            });
        });
    }

    public claimReward(accountAddress: string): Promise<any> {
        return new Promise<any>((resolve, reject) => {
            return this.web3Service.getStakingContract().then((contract: Contract) => {
                contract.methods.getReward().send({from: accountAddress}, (err, hash) => {
                    if (!err) {
                        this.recentTransactionsService.saveTransaction('Get reward', hash);
                        resolve(hash);
                    } else {
                        reject(err);
                    }
                });
            });
        });
    }

    public getRewardsXFT(accountAddress: string): void {
        if (this.rewardsXFT) {
            this.rewardsXFTChange.next(this.rewardsXFT);
        } else {
            this.fetchRewardsXFT(accountAddress);
        }
    }

    public fetchRewardsXFT(accountAddress: string): void {
        this.web3Service.getStakingContract().then((contract: Contract) => {
            contract.methods.earnedXFT(accountAddress).call({from: accountAddress}).then((res: string) => {
                this.rewardsXFT = this.utilsService.parseAmount(res, DIVIDER_FOR_BALANCE);
                this.rewardsXFT = this.rewardsXFT / 100;
                this.rewardsXFTChange.next(this.rewardsXFT);
            });
        });
    }

    public getRewardsSushi(accountAddress: string): void {
        if (this.rewardsSushi) {
            this.rewardsSushiChange.next(this.rewardsSushi);
        } else {
            this.fetchRewardsSushi(accountAddress);
        }
    }

    public fetchRewardsSushi(accountAddress: string): void {
        this.web3Service.getStakingContract().then((contract: Contract) => {
            contract.methods.earnedSushi(accountAddress).call({from: accountAddress}).then((res: string) => {
                this.rewardsSushi = this.utilsService.parseAmount(res, DIVIDER_FOR_BALANCE);
                this.rewardsSushi = this.rewardsSushi / 100;
                this.rewardsSushiChange.next(this.rewardsSushi);
            });
        });
    }

    public getAPYSushi(accountAddress: string): void {
        if (this.apySushi) {
            this.apySushiChange.next(this.apySushi);
        } else {
            this.fetchAPYSushi(accountAddress).then();
        }
    }

    public async fetchAPYSushi(accountAddress: string) {
        const info = await sushiData.sushi.info();
        const masterchefInfo = await sushiData.masterchef.info();
        const derivedETH =  info.derivedETH * Math.pow(10, 18);
        this.web3Service.getSlpContract().then((slpContract: Contract) => {
            slpContract.methods.totalSupply().call({from: accountAddress}).then((totalSupplyResult: any) => {
                const totalSupply = totalSupplyResult;
                this.web3Service.getOnsenContract().then((onsenContract: Contract) => {
                    onsenContract.methods.poolInfo(PID).call({from: accountAddress}).then((rewardPerBlockResult: any) => {
                        const allocPoint = rewardPerBlockResult['allocPoint'];
                        slpContract.methods.balanceOf(ONSEN_CONTRACT_ADDRESS_MAIN_NET).call({from: accountAddress}).then((slpBalanceResult: string) => {
                            const slpBalance = slpBalanceResult;
                            slpContract.methods.getReserves().call({from: accountAddress}).then((reservesResult: any) => {
                                const totalValueETH = reservesResult['_reserve1'];

                                onsenContract.methods.sushiPerBlock().call({from: accountAddress}).then((sushiPerBlockResult: any) => {
                                    const sushiPerBlock = sushiPerBlockResult;

                                    this.apySushi = this.calcSushiAPY(
                                        derivedETH,
                                        sushiPerBlock,
                                        allocPoint,
                                        masterchefInfo.totalAllocPoint,
                                        totalValueETH,
                                        slpBalance,
                                        totalSupply
                                    );
                                    this.apySushi = this.apySushi * 100;
                                    this.apySushi = this.utilsService.parseAmount(this.apySushi.toString(), 16) / 100  ;
                                    this.apySushiChange.next(this.apySushi);
                                });
                            });
                        });

                    });

                });
            });


        });

    }

    public getAPYXft(accountAddress: string): void {
        if (this.apyXFT) {
            this.apyXFTChange.next(this.apyXFT);
        } else {
            this.fetchAPYXft(accountAddress);
        }
    }

    public fetchAPYXft(accountAddress: string) {
        this.web3Service.getStakingContract().then((stakingContract: Contract) => {
            stakingContract.methods.rewardRate().call({from: accountAddress}).then((rewardRateResult: any) => {
                const rewardRate = rewardRateResult;
                stakingContract.methods.totalStaked().call({from: accountAddress}).then((totalStakedResult: any) => {
                    const totalStaked = totalStakedResult;
                    this.web3Service.getSlpContract().then((slpContract: Contract) => {
                        slpContract.methods.totalSupply().call({from: accountAddress}).then((totalSupplyResult: any) => {
                            const totalSupply = totalSupplyResult;
                            slpContract.methods.getReserves().call({from: accountAddress}).then((reserveResult: any) => {
                                const reserve = reserveResult._reserve0;
                                this.apyXFT = ((rewardRate * XFT_APY_CONST) / ((totalStaked / totalSupply ) * 2 * reserve)) * 100;
                                this.apyXFT = this.apyXFT.toFixed(2);
                                this.apyXFTChange.next(this.apyXFT);
                            });
                        });
                    });
                });
            });

        });
    }

    private calcSushiAPY = (derivedETH, sushiPerBlock, allocPoint, totalAllocPoint, totalValueETH, slpBalance, totalSupply) => {
        return (
            (derivedETH * BLOCKS_PER_DAY * sushiPerBlock * 3 * 365 * (allocPoint / totalAllocPoint) ) / (totalValueETH * 2 * (slpBalance / totalSupply)));
    }
}