import { ContractFactory, ContractRunner, ethers, Interface, Signer, BaseContract } from 'ethers';
// import KAMI721CABI from '../abis/KAMI721C.json';
import { KAMI721C } from '../contracts/KAMI721C';
// Import ABIs for implementation and proxy
import implementationCompiledContract from '../abis/KAMI721CUpgradeable/KAMI721CUpgradeable.json';
import proxyCompiledContract from '../abis/TransparentUpgradeableProxy/KAMITransparentUpgradeableProxy.json';

/**
 * Factory class for deploying KAMI721C contracts (Standard and Upgradeable via Proxy)
 */
export class KAMI721CFactory {
	private implementationFactory: ContractFactory;
	private proxyFactory: ContractFactory;
	private runner: ContractRunner;

	/**
	 * Creates a new instance of the KAMI721C factory
	 * @param runner A signer or provider with deployment permissions
	 */
	constructor(runner: ContractRunner) {
		this.runner = runner;
		// Create factory for the implementation contract
		this.implementationFactory = new ContractFactory(
			implementationCompiledContract.abi,
			implementationCompiledContract.bytecode,
			runner
		);
		// Create factory for the proxy contract
		this.proxyFactory = new ContractFactory(proxyCompiledContract.abi, proxyCompiledContract.bytecode, runner);
	}

	/**
	 * Deploy a new KAMI721C contract (Upgradeable via Transparent Proxy)
	 * @param usdcAddress The address of the USDC token contract
	 * @param name The name of the NFT collection
	 * @param symbol The symbol of the NFT collection
	 * @param baseURI The base URI for token metadata
	 * @param initialMintPrice The initial mint price in USDC (with 6 decimals)
	 * @param platformAddress The address that will receive platform commissions (also becomes proxy admin)
	 * @param platformCommissionPercentage The platform commission percentage in basis points (e.g., 500 = 5%)
	 * @param ownerAddress The address to grant the OWNER_ROLE to initially
	 * @returns The deployed contract instance, interacting with the proxy
	 */
	async deployUpgradeable(
		usdcAddress: string,
		name: string,
		symbol: string,
		baseURI: string,
		initialMintPrice: bigint | string = 1000000n, // Default 1 USDC (6 decimals)
		platformAddress?: string,
		platformCommissionPercentage: number = 500, // Default 5%
		ownerAddress?: string
	): Promise<KAMI721C> {
		let finalPlatformAddress = platformAddress;
		let finalOwnerAddress = ownerAddress;

		// Determine deployer address if needed
		let deployerAddress: string | undefined;
		if (this.runner && 'getAddress' in this.runner) {
			deployerAddress = await (this.runner as Signer).getAddress();
		} else {
			// If runner is not a signer, addresses must be provided
			if (!finalPlatformAddress || !finalOwnerAddress) {
				throw new Error('platformAddress and ownerAddress are required when using a provider');
			}
		}

		// If addresses not provided, use the deployer's address
		if (!finalPlatformAddress && deployerAddress) {
			finalPlatformAddress = deployerAddress;
			console.log(`Using deployer address ${deployerAddress} as platform address and proxy admin.`);
		}
		if (!finalOwnerAddress && deployerAddress) {
			finalOwnerAddress = deployerAddress;
			console.log(`Using deployer address ${deployerAddress} as initial owner.`);
		}

		// Ensure addresses are set
		if (!finalPlatformAddress || !finalOwnerAddress) {
			throw new Error('Could not determine platformAddress or ownerAddress.');
		}

		console.log('Deploying KAMI721CUpgradeable implementation...');
		try {
			const implementationContract = await this.implementationFactory.deploy({ gasLimit: 5000000 });
			await implementationContract.waitForDeployment();
			const implementationAddress = await implementationContract.getAddress();
			console.log('Implementation contract deployed at:', implementationAddress);

			// Verify deployment
			const provider = this.runner.provider;
			if (!provider) {
				throw new Error('Runner does not have a provider.');
			}
			const code = await provider.getCode(implementationAddress);
			if (!code || code === '0x') {
				throw new Error('Implementation deployment failed - no code at address');
			}

			// Prepare initialization calldata
			const implementationInterface = new Interface(implementationCompiledContract.abi);
			const initializeData = implementationInterface.encodeFunctionData('initialize', [
				usdcAddress,
				name,
				symbol,
				baseURI,
				initialMintPrice,
				finalPlatformAddress,
				platformCommissionPercentage,
				finalOwnerAddress,
			]);
			console.log('Prepared initialization calldata.');

			// Deploy the proxy contract
			console.log('Deploying KAMITransparentUpgradeableProxy...');
			const proxyContract = await this.proxyFactory.deploy(
				implementationAddress,
				finalPlatformAddress, // Use platformAddress as proxy admin
				initializeData,
				{ gasLimit: 2000000 } // Gas limit for proxy deployment
			);
			await proxyContract.waitForDeployment();
			const proxyAddress = await proxyContract.getAddress();
			console.log('Proxy contract deployed at:', proxyAddress);

			// Verify proxy deployment
			const proxyCode = await provider.getCode(proxyAddress);
			if (!proxyCode || proxyCode === '0x') {
				throw new Error('Proxy deployment failed - no code at address');
			}

			// Return a contract instance attached to the proxy address, using the implementation ABI
			console.log(`Returning contract instance attached to proxy address ${proxyAddress}`);
			return new KAMI721C(this.runner, proxyAddress, implementationCompiledContract.abi);
		} catch (error: any) {
			console.error('Deployment failed:', {
				error: error.message,
				reason: error.reason,
				code: error.code,
				data: error.data,
				transaction: error.transaction,
			});
			throw error;
		}
	}

	// Keep the old deploy method for standard (non-upgradeable) deployment if needed,
	// or remove/deprecate it.
	/**
	 * Deploy a new KAMI721C contract (Standard Non-Upgradeable Version)
	 * @deprecated Use deployUpgradeable instead for the proxy-based version.
	 * @param usdcAddress The address of the USDC token contract
	 * @param name The name of the NFT collection
	 * @param symbol The symbol of the NFT collection
	 * @param baseURI The base URI for token metadata
	 * @param initialMintPrice The initial mint price in USDC (with 6 decimals)
	 * @param platformAddress The address that will receive platform commissions
	 * @param platformCommissionPercentage The platform commission percentage in basis points (e.g., 500 = 5%)
	 * @returns The deployed contract instance
	 */
	async deployStandard(
		usdcAddress: string,
		name: string,
		symbol: string,
		baseURI: string,
		initialMintPrice: bigint | string = 1000000n, // Default 1 USDC (6 decimals)
		platformAddress?: string,
		platformCommissionPercentage: number = 500 // Default 5%
	): Promise<KAMI721C> {
		console.warn('Deploying standard non-upgradeable KAMI721C. Use deployUpgradeable for proxy deployment.');
		let finalPlatformAddress = platformAddress;

		// If platform address not provided, use the deployer's address
		if (!finalPlatformAddress) {
			if (this.runner && 'getAddress' in this.runner) {
				finalPlatformAddress = await (this.runner as Signer).getAddress();
			} else {
				throw new Error('Platform address is required when deployer address cannot be determined');
			}
		}

		console.log('Deploying standard contract with parameters:', {
			usdcAddress,
			name,
			symbol,
			baseURI,
			initialMintPrice,
			finalPlatformAddress,
			platformCommissionPercentage,
		});

		// Use the original KAMI721C ABI/Bytecode for standard deployment
		// Assuming the old ABI is still needed and available
		const standardCompiledContract = await import('../abis/KAMI721C/KAMI721C.json');
		const standardFactory = new ContractFactory(standardCompiledContract.abi, standardCompiledContract.bytecode, this.runner);

		try {
			console.log('Creating standard deployment transaction...');
			const contract = (await standardFactory.deploy(
				usdcAddress,
				name,
				symbol,
				baseURI,
				initialMintPrice,
				finalPlatformAddress,
				platformCommissionPercentage,
				{ gasLimit: 5000000 }
			)) as BaseContract;

			console.log('Waiting for standard deployment transaction to be mined...');
			await contract.waitForDeployment();
			const address = await contract.getAddress();
			console.log('Standard contract deployed at address:', address);

			// Verify the contract was deployed successfully
			console.log('Verifying standard contract deployment...');
			const provider = this.runner.provider;
			if (!provider) {
				throw new Error('Runner does not have a provider.');
			}
			const code = await provider.getCode(address);
			if (!code || code === '0x') {
				throw new Error('Standard contract deployment failed - no code at contract address');
			}
			console.log('Standard contract code verified successfully');

			return new KAMI721C(this.runner, address, standardCompiledContract.abi);
		} catch (error: any) {
			console.error('Standard deployment failed:', {
				error: error.message,
				reason: error.reason,
				code: error.code,
				data: error.data,
				transaction: error.transaction,
			});
			throw error;
		}
	}
}
