import { ethers } from 'ethers';
import { KAMI721C } from '../contracts/KAMI721C';
import { colorLog } from '../utils/console-colors';
import { KAMI721CFactory } from '../factories/KAMI721CFactory';
import dotenv from 'dotenv';

dotenv.config();

const IERC20_ABI = [
	'function approve(address spender, uint256 amount) returns (bool)',
	'function balanceOf(address account) view returns (uint256)',
	'function decimals() view returns (uint8)',
	'function transfer(address to, uint256 amount) returns (bool)',
	'function allowance(address owner, address spender) view returns (uint256)',
];

async function main() {
	try {
		// Get provider and wallet
		const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
		const wallet = new ethers.Wallet(process.env.PRIVATE_KEY!, provider);
		console.log(`Connected with wallet: ${wallet.address}`);

		// Get starting nonce
		const startNonce = await wallet.getNonce();
		console.log(`Starting nonce: ${startNonce}`);

		// Deploy a new contract instance using the upgradeable proxy
		const factory = new KAMI721CFactory(wallet);
		console.log('Deploying upgradeable contract via proxy...');
		const nftContract = await factory.deployUpgradeable(
			process.env.USDC_ADDRESS!,
			'KAMI Special NFT Collection (Upgradeable)', // Updated name for clarity
			'KAMIU', // Updated symbol for clarity
			'https://api.kami.com/nft/upgradeable/', // Updated URI for clarity
			1000000n, // 1 USDC mint price
			wallet.address, // Platform address (will also be proxy admin)
			500, // 5% platform commission
			wallet.address // Initial owner address
		);
		console.log(`Proxy contract deployed at: ${nftContract.getAddress()}`);
		console.log(`Contract Name: ${await nftContract.name()}`);
		console.log(`Total supply: ${await nftContract.totalSupply()}`);

		// Create renter wallet
		const renterWallet = ethers.Wallet.createRandom().connect(provider);
		console.log(`Created renter wallet: ${renterWallet.address}`);

		// Fund renter wallet with ETH for gas
		const fundingTx = await wallet.sendTransaction({
			to: renterWallet.address,
			value: ethers.parseEther('0.01'),
		});
		await fundingTx.wait();
		console.log('Funded renter wallet with 0.01 ETH for gas');

		// Get USDC contract
		const usdcAddress = process.env.USDC_ADDRESS!;
		const usdcContract = new ethers.Contract(usdcAddress, IERC20_ABI, wallet);
		const renterUsdcContract = new ethers.Contract(usdcAddress, IERC20_ABI, renterWallet);

		// Get mint price
		const mintPrice = await nftContract.mintPrice();
		const decimals = await usdcContract.decimals();
		console.log(`Mint price: ${ethers.formatUnits(mintPrice, Number(decimals))} USDC`);

		// Approve USDC spending for minting
		console.log('Approving USDC spending for minting...');
		const approvalTx = await usdcContract.approve(nftContract.getAddress(), mintPrice);
		await approvalTx.wait();
		console.log('USDC spending approved for minting');

		// Mint a token
		console.log('Minting a token...');
		const mintTx = await nftContract.mint();
		await mintTx.wait();
		console.log('Token minted successfully');

		// Find the minted token
		const tokenId = 0; // First token minted
		console.log(`Using token #${tokenId}`);

		// Get owner's USDC balance
		const ownerBalance = await usdcContract.balanceOf(wallet.address);
		console.log(`Owner USDC balance: ${ethers.formatUnits(ownerBalance, decimals)} USDC`);

		// Set up rental parameters
		const rentalDuration = 86400; // 1 day in seconds
		const baseRentalPrice = ethers.parseUnits('1.0', decimals); // 1.0 USDC

		// Calculate total rental price including platform commission
		const platformCommission = await nftContract.getPlatformCommissionPercentage();
		const platformCommissionAmount = (baseRentalPrice * platformCommission) / 10000n;
		const totalRentalPrice = baseRentalPrice + platformCommissionAmount;

		console.log(`Base rental price: ${ethers.formatUnits(baseRentalPrice, decimals)} USDC`);
		console.log(
			`Platform commission (${Number(platformCommission) / 100}%): ${ethers.formatUnits(platformCommissionAmount, decimals)} USDC`
		);
		console.log(`Total rental price: ${ethers.formatUnits(totalRentalPrice, decimals)} USDC`);

		// Transfer enough USDC to cover rental price and platform commission
		const transferAmount = totalRentalPrice;
		console.log(`Transferring ${ethers.formatUnits(transferAmount, decimals)} USDC to renter...`);
		const transferTx = await usdcContract.transfer(renterWallet.address, transferAmount);
		await transferTx.wait();
		console.log('USDC transferred to renter');

		// Get renter's USDC balance
		const renterInitialBalance = await usdcContract.balanceOf(renterWallet.address);
		console.log(`Renter USDC balance: ${ethers.formatUnits(renterInitialBalance, decimals)} USDC`);

		// Approve USDC spending for rental
		console.log('Approving USDC spending for rental...');
		const rentalApprovalTx = await renterUsdcContract.approve(nftContract.getAddress(), totalRentalPrice);
		await rentalApprovalTx.wait();
		console.log('USDC spending approved for rental');

		console.log(`Token #${tokenId} exists and is owned by: ${await nftContract.ownerOf(tokenId)}`);
		console.log(`Renter starting nonce: ${await renterWallet.getNonce()}`);

		// Check and grant necessary roles
		const ownerRole = await nftContract.OWNER_ROLE();
		const platformRole = await nftContract.PLATFORM_ROLE();
		const hasOwnerRole = await nftContract.hasRole(ownerRole, wallet.address);
		const hasPlatformRole = await nftContract.hasRole(platformRole, wallet.address);

		console.log(`Wallet has OWNER_ROLE: ${hasOwnerRole}`);
		console.log(`Wallet has PLATFORM_ROLE: ${hasPlatformRole}`);

		// Grant roles if needed
		if (!hasOwnerRole) {
			console.log('Granting OWNER_ROLE to wallet...');
			const grantOwnerTx = await nftContract.grantRole(ownerRole, wallet.address);
			await grantOwnerTx.wait();
			console.log('OWNER_ROLE granted successfully');
		}

		if (!hasPlatformRole) {
			console.log('Granting PLATFORM_ROLE to wallet...');
			const grantPlatformTx = await nftContract.grantRole(platformRole, wallet.address);
			await grantPlatformTx.wait();
			console.log('PLATFORM_ROLE granted successfully');
		}

		// Attempt to rent the token directly
		console.log(`Attempting to rent token #${tokenId}...`);
		try {
			// Check rental conditions
			console.log('Checking rental conditions...');

			// 1. Verify token exists and get owner
			console.log(`Checking if token #${tokenId} exists...`);
			try {
				const tokenOwner = await nftContract.ownerOf(tokenId);
				console.log(`Token owner: ${tokenOwner}`);

				// 2. Verify renter is not the token owner
				if (renterWallet.address === tokenOwner) {
					throw new Error('Renter cannot be the token owner');
				}
				console.log('Renter is not the token owner ✓');

				// 3. Check USDC allowance
				const allowance = await usdcContract.allowance(renterWallet.address, nftContract.getAddress());
				console.log(`Current USDC allowance: ${ethers.formatUnits(allowance, decimals)} USDC`);
				if (allowance < totalRentalPrice) {
					throw new Error('Insufficient USDC allowance');
				}
				console.log('USDC allowance is sufficient ✓');

				// 4. Check USDC balance
				const renterBalance = await usdcContract.balanceOf(renterWallet.address);
				console.log(`Renter USDC balance: ${ethers.formatUnits(renterBalance, decimals)} USDC`);
				if (renterBalance < totalRentalPrice) {
					throw new Error('Insufficient USDC balance');
				}
				console.log('USDC balance is sufficient ✓');

				// 5. Try to check if token is already rented
				try {
					const isRented = await nftContract.isRented(tokenId);
					console.log(`Token rental status: ${isRented ? 'Rented' : 'Not rented'}`);

					if (isRented) {
						throw new Error('Token is already rented');
					}
					console.log('Token is available for rent ✓');

					// 6. Attempt the rental
					console.log('All conditions met, attempting rental...');
					try {
						// Connect renter wallet to the contract instance
						const renterNftContract = nftContract.connect(renterWallet);
						const rentalTx = await renterNftContract.rentToken(tokenId, rentalDuration, totalRentalPrice);
						console.log('Rental transaction sent:', rentalTx.hash);

						const receipt = await rentalTx.wait();
						console.log('Rental transaction confirmed:', receipt.hash);

						// Verify the rental was successful
						const newRentalStatus = await nftContract.isRented(tokenId);
						console.log(`Token is now rented: ${newRentalStatus}`);
					} catch (error: any) {
						console.error('Failed to perform rental operation:', error);
						if (error.data) {
							console.error('Error data:', error.data);
						}
						if (error.transaction) {
							console.error('Transaction data:', error.transaction);
						}
						if (error.receipt) {
							console.error('Transaction receipt:', error.receipt);
						}
						throw error;
					}
				} catch (error: any) {
					console.error('Failed to check rental status:', error);
					if (error.data) {
						console.error('Rental status error data:', error.data);
					}
					if (error.transaction) {
						console.error('Rental status transaction data:', error.transaction);
					}
					throw error;
				}
			} catch (error: any) {
				console.error('Failed to verify token existence:', error);
				if (error.data) {
					console.error('Token existence error data:', error.data);
				}
				if (error.transaction) {
					console.error('Token existence transaction data:', error.transaction);
				}
				throw error;
			}
		} catch (error: any) {
			console.error('Failed to perform rental operation:', error);
			if (error.data) {
				console.error('Error data:', error.data);
			}
			if (error.transaction) {
				console.error('Transaction data:', error.transaction);
			}
			throw error;
		}
	} catch (error: any) {
		console.error('Error:', error);
		if (error.data) {
			console.error('Error data:', error.data);
		}
		if (error.transaction) {
			console.error('Transaction:', error.transaction);
		}
	}
}

main();
