#!/usr/bin/env python3
"""
Script to calculate APY for all PartyPools.
Fetches all pools from PartyPlanner and calculates APY for each one.
"""

from web3 import Web3
from datetime import datetime
import json
import os

# Configuration
RPC_URL = "https://eth-sepolia.g.alchemy.com/v2/sJ7rLYKJzdRqXUs9cOiLVvHN8aTI30dn"
CHAIN_ID = 11155111
DEPLOYMENT_JSON_PATH = "../liqp-deployments.json"
DEPLOYMENT_ARTIFACTS_PATH = "../deployment"
TIME_RANGE_SECONDS = 3600  # 1 hour

# ABIs will be loaded dynamically from deployment artifacts
ERC20_ABI = []
PARTY_INFO_ABI = []
PARTY_PLANNER_ABI = []
POOL_ABI = []


def load_abi_from_deployment(chain_id, contract_name):
    """Load ABI from deployment artifacts"""
    script_dir = os.path.dirname(os.path.abspath(__file__))
    artifact_path = os.path.join(
        script_dir,
        DEPLOYMENT_ARTIFACTS_PATH,
        str(chain_id),
        'v1',
        'out',
        f'{contract_name}.sol',
        f'{contract_name}.json'
    )

    try:
        with open(artifact_path, 'r') as f:
            artifact_data = json.load(f)
            return artifact_data.get('abi', [])
    except Exception as e:
        print(f"Error loading ABI for {contract_name}: {e}")
        return []


def q64_64_to_float(q64_64_value):
    """Convert ABDK Q64.64 fixed-point to float"""
    return q64_64_value / (2 ** 64)


def find_block_by_timestamp(w3, target_timestamp, low, high):
    """Binary search to find block closest to target timestamp"""
    if low >= high:
        return low
    mid = (low + high) // 2
    mid_block = w3.eth.get_block(mid)
    mid_timestamp = mid_block['timestamp']
    if mid_timestamp < target_timestamp:
        return find_block_by_timestamp(w3, target_timestamp, mid + 1, high)
    else:
        return find_block_by_timestamp(w3, target_timestamp, low, mid)


def calculate_pool_apy(w3, pool_address, party_info_contract, from_block, current_block):
    """Calculate APY for a single pool"""
    try:
        pool_address = Web3.to_checksum_address(pool_address)
        pool_contract = w3.eth.contract(address=pool_address, abi=POOL_ABI)
        token_addresses = pool_contract.functions.allTokens().call()

        # Get token info and calculate TVL
        token_info = {}
        stablecoin_decimals = None
        pool_tvl_usd = 0.0
        token_symbols = []

        for idx, token_addr in enumerate(token_addresses):
            token_contract = w3.eth.contract(address=token_addr, abi=ERC20_ABI)
            decimals = token_contract.functions.decimals().call()
            symbol = token_contract.functions.symbol().call()
            token_symbols.append(symbol)
            balance_raw = token_contract.functions.balanceOf(pool_address).call()
            balance_human = balance_raw / (10 ** decimals)

            if idx == 0:
                stablecoin_decimals = decimals
                price_usd = 1.0
            else:
                price_q64_64 = party_info_contract.functions.price(pool_address, idx, 0).call()
                price_ratio = q64_64_to_float(price_q64_64)
                decimal_adjustment = 10 ** (stablecoin_decimals - decimals)
                price_usd = price_ratio * decimal_adjustment

            token_value_usd = balance_human * price_usd
            pool_tvl_usd += token_value_usd
            token_info[token_addr.lower()] = {'decimals': decimals, 'price_usd': price_usd}

        # Fetch events and calculate fees
        total_fees_usd = 0.0
        event_count = 0

        # Process Swap events
        try:
            logs = pool_contract.events.Swap().get_logs(from_block=from_block, to_block=current_block)
            event_count += len(logs)
            for log in logs:
                token = token_info.get(log['args']['tokenIn'].lower())
                if token:
                    fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
                    total_fees_usd += fee
                    print(f"    Swap - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
        except Exception as e:
            print(f"    Error fetching Swap events: {e}")

        # Process SwapMint events
        try:
            logs = pool_contract.events.SwapMint().get_logs(from_block=from_block, to_block=current_block)
            event_count += len(logs)
            for log in logs:
                token = token_info.get(log['args']['tokenIn'].lower())
                if token:
                    fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
                    total_fees_usd += fee
                    print(f"    SwapMint - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
        except Exception as e:
            print(f"    Error fetching SwapMint events: {e}")

        # Process BurnSwap events
        try:
            logs = pool_contract.events.BurnSwap().get_logs(from_block=from_block, to_block=current_block)
            event_count += len(logs)
            for log in logs:
                token = token_info.get(log['args']['tokenOut'].lower())
                if token:
                    fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
                    total_fees_usd += fee
                    print(f"    BurnSwap - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
        except Exception as e:
            print(f"    Error fetching BurnSwap events: {e}")

        # Process Flash events
        try:
            logs = pool_contract.events.Flash().get_logs(from_block=from_block, to_block=current_block)
            event_count += len(logs)
            for log in logs:
                token = token_info.get(log['args']['token'].lower())
                if token:
                    fee = (log['args']['lpFee'] + log['args']['protocolFee']) / (10 ** token['decimals']) * token['price_usd']
                    total_fees_usd += fee
                    print(f"    Flash - Block {log['blockNumber']}, TX {log['transactionHash'].hex()[:10]}..., Fee: ${fee:.6f}")
        except Exception as e:
            print(f"    Error fetching Flash events: {e}")

        # Calculate APY
        if pool_tvl_usd > 0 and TIME_RANGE_SECONDS:
            days = TIME_RANGE_SECONDS / 86400
            daily_return = (total_fees_usd / pool_tvl_usd) / days
            apy = (pow(1 + daily_return, 365) - 1) * 100
        else:
            apy = 0

        return {
            'pool_address': pool_address,
            'tokens': token_symbols,
            'tvl_usd': pool_tvl_usd,
            'fees_usd': total_fees_usd,
            'event_count': event_count,
            'apy': apy
        }
    except Exception as e:
        print(f"Error processing pool {pool_address}: {e}")
        return None


def main():
    w3 = Web3(Web3.HTTPProvider(RPC_URL))

    if not w3.is_connected():
        print(f"Error: Cannot connect to RPC node at {RPC_URL}")
        return

    chain_id = w3.eth.chain_id
    print(f"Connected to chain {chain_id}")
    print(f"Current block: {w3.eth.block_number}")

    # Load ABIs from deployment artifacts
    print("Loading ABIs from deployment artifacts...")
    erc20_abi = load_abi_from_deployment(chain_id, 'ERC20')
    party_info_abi = load_abi_from_deployment(chain_id, 'PartyInfo')
    party_planner_abi = load_abi_from_deployment(chain_id, 'PartyPlanner')
    pool_abi = load_abi_from_deployment(chain_id, 'PartyPool')

    if not erc20_abi or not party_info_abi or not party_planner_abi or not pool_abi:
        print("Error: Failed to load one or more ABIs from deployment artifacts")
        return

    # Update global ABIs
    global ERC20_ABI, PARTY_INFO_ABI, PARTY_PLANNER_ABI, POOL_ABI
    ERC20_ABI = erc20_abi
    PARTY_INFO_ABI = party_info_abi
    PARTY_PLANNER_ABI = party_planner_abi
    POOL_ABI = pool_abi

    # Load deployment addresses
    # tim is adding comments
    script_dir = os.path.dirname(os.path.abspath(__file__))
    deployment_path = os.path.join(script_dir, DEPLOYMENT_JSON_PATH)

    try:
        with open(deployment_path, 'r') as f:
            deployment_data = json.load(f)
            chain_data = deployment_data.get(str(chain_id), {}).get('v1', {})
            party_info_address = chain_data.get('PartyInfo')
            party_planner_address = chain_data.get('PartyPlanner')

            if not party_info_address or not party_planner_address:
                print(f"Error: PartyInfo or PartyPlanner not found in deployment JSON")
                return
    except Exception as e:
        print(f"Error: Could not load deployment JSON: {e}")
        return

    party_info_address = Web3.to_checksum_address(party_info_address)
    party_planner_address = Web3.to_checksum_address(party_planner_address)

    print(f"PartyInfo: {party_info_address}")
    print(f"PartyPlanner: {party_planner_address}")

    # Get all pools from PartyPlanner
    party_planner = w3.eth.contract(address=party_planner_address, abi=PARTY_PLANNER_ABI)
    party_info_contract = w3.eth.contract(address=party_info_address, abi=PARTY_INFO_ABI)

    pool_count = party_planner.functions.poolCount().call()
    print(f"\nTotal pools: {pool_count}")

    if pool_count == 0:
        print("No pools found")
        return

    # Get all pools
    all_pools = party_planner.functions.getAllPools(0, pool_count).call()
    print(f"Fetched {len(all_pools)} pool addresses\n")

    # Calculate block range for last 24 hours
    current_block = w3.eth.block_number
    current_timestamp = w3.eth.get_block(current_block)['timestamp']
    target_timestamp = current_timestamp - TIME_RANGE_SECONDS
    from_block = find_block_by_timestamp(w3, target_timestamp, 0, current_block)

    print(f"Analyzing events from block {from_block} to {current_block}")
    print(f"Time range: last {TIME_RANGE_SECONDS / 3600:.1f} hours\n")

    # Calculate APY for each pool
    print(f"{'='*110}")
    print(f"{'Pool Address':<45} {'Tokens':<20} {'TVL (USD)':<15} {'Fees (24h)':<15} {'Events':<8} {'APY':<10}")
    print(f"{'='*110}")

    results = []
    for pool_addr in all_pools:
        result = calculate_pool_apy(w3, pool_addr, party_info_contract, from_block, current_block)
        if result:
            results.append(result)
            token_str = '/'.join(result['tokens'][:3])
            print(f"{result['pool_address']:<45} {token_str:<20} ${result['tvl_usd']:>13,.2f} ${result['fees_usd']:>13,.6f} {result['event_count']:>7} {result['apy']:>9.2f}%")

    print(f"{'='*110}")
    print(f"\nSummary:")
    print(f"  Total Pools: {len(results)}")
    print(f"  Total TVL: ${sum(r['tvl_usd'] for r in results):,.2f}")
    print(f"  Total 24h Fees: ${sum(r['fees_usd'] for r in results):,.6f}")
    if results:
        print(f"  Avg APY: {sum(r['apy'] for r in results) / len(results):.2f}%")


if __name__ == "__main__":
    main()
