import type { Provider, Signer } from 'ethers';
import { AdapterBase } from '../sdk/adapter-base';
import { AdapterReceiveResult, AdapterSendResult, AdapterValidateResult } from '../sdk/adapter-types';

/**
 * dbis.adapter.thirdweb
 *
 * Thirdweb Gateway Adapter — contract invocation via Thirdweb SDK (not a bank messaging rail).
 * **ABI / `encodeMethodCall`:** not production-complete — throws until ABI-backed encoding is wired (PG-GW-009).
 */
export class ThirdwebAdapter extends AdapterBase {
	private clientId?: string;
	private secretKey?: string;
	private chainConfig: Map<number, { rpcUrl: string; contractAddresses: Record<string, string> }>;
	private privateKey?: string;
	private signer?: unknown; // Thirdweb signer instance

	constructor() {
		super();
		this.chainConfig = new Map();
	}

	async initialize(config: Record<string, unknown>, secretsRef?: string): Promise<void> {
		await super.initialize(config, secretsRef);

		// Extract Thirdweb configuration
		this.clientId = config.clientId as string;
		this.secretKey = config.secretKey as string;
		this.privateKey = config.privateKey as string;

		// Parse chain configuration
		if (config.chainConfig && typeof config.chainConfig === 'object') {
			const chainConfigObj = config.chainConfig as Record<string, unknown>;
			for (const [chainIdStr, chainConfig] of Object.entries(chainConfigObj)) {
				const chainId = parseInt(chainIdStr, 10);
				if (!isNaN(chainId) && typeof chainConfig === 'object' && chainConfig !== null) {
					const cfg = chainConfig as Record<string, unknown>;
					this.chainConfig.set(chainId, {
						rpcUrl: cfg.rpcUrl as string,
						contractAddresses: (cfg.contractAddresses as Record<string, string>) || {},
					});
				}
			}
		}

		// Initialize signer if private key is provided
		if (this.privateKey) {
			await this.initializeSigner();
		}
	}

	async capabilities(): Promise<string[]> {
		return [
			'CONTRACT_INVOCATION',
			'MULTI_CHAIN',
			'ACCOUNT_ABSTRACTION',
			'TRANSACTION_TRACKING',
			'EVENT_LISTENING',
		];
	}

	async validate(canonicalInstruction: Record<string, unknown>): Promise<AdapterValidateResult> {
		const errors: Array<{ code: string; message: string }> = [];

		// Validate required fields
		if (!canonicalInstruction.chainId) {
			errors.push({ code: 'MISSING_CHAIN_ID', message: 'chainId is required' });
		}

		if (!canonicalInstruction.contractAddress) {
			errors.push({ code: 'MISSING_CONTRACT_ADDRESS', message: 'contractAddress is required' });
		}

		if (!canonicalInstruction.method) {
			errors.push({ code: 'MISSING_METHOD', message: 'method is required' });
		}

		// Validate chain configuration
		if (canonicalInstruction.chainId) {
			const chainId = typeof canonicalInstruction.chainId === 'number'
				? canonicalInstruction.chainId
				: parseInt(String(canonicalInstruction.chainId), 10);

			if (!this.chainConfig.has(chainId)) {
				errors.push({
					code: 'UNSUPPORTED_CHAIN',
					message: `Chain ${chainId} is not configured`,
				});
			}
		}

		// Validate contract address format
		if (canonicalInstruction.contractAddress) {
			const address = String(canonicalInstruction.contractAddress);
			if (!/^0x[a-fA-F0-9]{40}$/.test(address)) {
				errors.push({
					code: 'INVALID_CONTRACT_ADDRESS',
					message: 'contractAddress must be a valid Ethereum address',
				});
			}
		}

		if (errors.length > 0) {
			return { ok: false, errors };
		}

		return { ok: true };
	}

	async send(outboxRecord: {
		txnId: string;
		payloadHash: string;
		envelope: unknown;
	}): Promise<AdapterSendResult> {
		try {
			const envelope = outboxRecord.envelope as Record<string, unknown>;
			
			// Validate instruction
			const validation = await this.validate(envelope);
			if (!validation.ok) {
				return {
					status: 'FAILED',
					error: {
						code: validation.errors?.[0]?.code || 'VALIDATION_ERROR',
						message: validation.errors?.[0]?.message || 'Validation failed',
						retryClass: 'NON_RETRYABLE',
					},
				};
			}

			const chainId = typeof envelope.chainId === 'number'
				? envelope.chainId
				: parseInt(String(envelope.chainId), 10);
			const contractAddress = String(envelope.contractAddress);
			const method = String(envelope.method);
			const params = (envelope.params as unknown[]) || [];

			// Get contract instance
			const contract = await this.getContract(chainId, contractAddress);

			// Execute contract method
			const txHash = await this.executeTransaction(
				contract,
				method,
				params,
				envelope.value as string | undefined,
				envelope.gasLimit as number | undefined,
				envelope.gasPrice as string | undefined,
			);

			// Wait for confirmation if requested
			let status: 'SENT' | 'FAILED' = 'SENT';
			if (envelope.waitForConfirmation === true) {
				const confirmed = await this.waitForConfirmation(txHash, chainId);
				if (!confirmed) {
					status = 'FAILED';
				}
			}

			return {
				status,
				railMessageId: txHash,
			};
		} catch (error) {
			return {
				status: 'FAILED',
				error: {
					code: 'EXECUTION_ERROR',
					message: error instanceof Error ? error.message : 'Unknown error',
					retryClass: this.mapErrorToRetryClass(error),
				},
			};
		}
	}

	async receive(rawRailMessage: unknown): Promise<AdapterReceiveResult> {
		try {
			const message = rawRailMessage as Record<string, unknown>;

			// Handle transaction receipt
			if (message.type === 'TRANSACTION_RECEIPT') {
				const receipt = message.receipt as Record<string, unknown>;
				const status = receipt.status === '0x1' || receipt.status === 1 ? 'ACK' : 'NAK';

				return {
					status,
					railMessageId: receipt.transactionHash as string,
					statusPayload: {
						blockNumber: receipt.blockNumber,
						gasUsed: receipt.gasUsed,
						status: receipt.status,
					},
				};
			}

			// Handle event
			if (message.type === 'EVENT') {
				return {
					status: 'STATUS',
					railMessageId: message.transactionHash as string,
					statusPayload: message.eventData as Record<string, unknown>,
				};
			}

			// Handle error
			if (message.type === 'ERROR') {
				return {
					status: 'NAK',
					railMessageId: message.transactionHash as string,
					error: {
						code: message.errorCode as string,
						message: message.errorMessage as string,
						retryClass: this.mapErrorToRetryClass(message),
					},
				};
			}

			return {
				status: 'STATUS',
				railMessageId: message.transactionHash as string || `MSG-${Date.now()}`,
				statusPayload: message,
			};
		} catch (error) {
			return {
				status: 'NAK',
				error: {
					code: 'PARSE_ERROR',
					message: error instanceof Error ? error.message : 'Failed to parse rail message',
					retryClass: 'NON_RETRYABLE',
				},
			};
		}
	}

	mapStatus(rail: { code: string; description?: string; source?: string }): {
		status: string;
		railStatus: { code: string; description?: string; source?: string };
	} {
		// Map Thirdweb transaction statuses
		const statusMap: Record<string, string> = {
			'PENDING': 'IN_PROGRESS',
			'CONFIRMED': 'ACK',
			'FAILED': 'FAILED',
			'REVERTED': 'FAILED',
			'MINED': 'ACK',
		};

		const status = statusMap[rail.code.toUpperCase()] || 'IN_PROGRESS';

		return {
			status,
			railStatus: rail,
		};
	}

	finalityHints(railStatus: { code: string }): { hint?: 'PROVISIONAL' | 'FINAL' | 'IRREVERSIBLE' } {
		// Map Thirdweb finality statuses
		const finalityMap: Record<string, 'PROVISIONAL' | 'FINAL' | 'IRREVERSIBLE'> = {
			'PENDING': 'PROVISIONAL',
			'CONFIRMED': 'FINAL',
			'MINED': 'FINAL',
			'FINALIZED': 'IRREVERSIBLE',
		};

		const hint = finalityMap[railStatus.code.toUpperCase()];
		return hint ? { hint } : {};
	}

	errorMap(railError: { code: string; message?: string }): {
		errorCode: string;
		retryClass: 'RETRYABLE' | 'NON_RETRYABLE' | 'MANUAL_REVIEW';
	} {
		const code = railError.code || 'UNKNOWN';

		// Map Thirdweb error codes to retry classes
		const retryableErrors = [
			'NETWORK_ERROR',
			'TIMEOUT',
			'RATE_LIMIT',
			'TEMPORARY_FAILURE',
		];

		const nonRetryableErrors = [
			'INVALID_CONTRACT',
			'INVALID_METHOD',
			'INSUFFICIENT_FUNDS',
			'CONTRACT_REVERTED',
			'VALIDATION_ERROR',
		];

		if (retryableErrors.some((e) => code.toUpperCase().includes(e))) {
			return { errorCode: code, retryClass: 'RETRYABLE' };
		}

		if (nonRetryableErrors.some((e) => code.toUpperCase().includes(e))) {
			return { errorCode: code, retryClass: 'NON_RETRYABLE' };
		}

		return { errorCode: code, retryClass: 'MANUAL_REVIEW' };
	}

	/**
	 * Initialize Thirdweb signer
	 */
	private async initializeSigner(): Promise<void> {
		if (!this.privateKey) {
			return;
		}

		try {
			// Dynamic import of Thirdweb SDK
			// Note: In production, you would install @thirdweb-dev/sdk
			// const { ThirdwebSDK } = await import('@thirdweb-dev/sdk');
			// For now, we'll use ethers.js as fallback
			const { ethers } = await import('ethers');
			
			// Create wallet from private key
			// In production, integrate with Thirdweb SDK properly
			// For now, store ethers wallet reference
			this.signer = {
				type: 'ethers',
				// In production: ThirdwebSDK.fromPrivateKey(this.privateKey, { ... })
			};
		} catch (error) {
			console.warn('Failed to initialize Thirdweb signer, will use RPC-only mode:', error);
		}
	}

	/**
	 * Get contract instance for a chain and address
	 */
	private async getContract(chainId: number, contractAddress: string): Promise<unknown> {
		const chainConfig = this.chainConfig.get(chainId);
		if (!chainConfig) {
			throw new Error(`Chain ${chainId} is not configured`);
		}

		// Validate contract address
		if (!/^0x[a-fA-F0-9]{40}$/.test(contractAddress)) {
			throw new Error(`Invalid contract address: ${contractAddress}`);
		}

		try {
			// Dynamic import of ethers.js for contract interaction
			const { ethers } = await import('ethers');

			// Create provider for the chain
			const provider = new ethers.JsonRpcProvider(chainConfig.rpcUrl);

			// If we have a signer, create contract with signer, otherwise read-only
			if (this.signer && this.privateKey) {
				const wallet = new ethers.Wallet(this.privateKey, provider);
				// Return contract instance (ABI would be provided in envelope or configuration)
				// For now, return a placeholder that can be used for method invocation
				return {
					type: 'contract',
					address: contractAddress,
					provider,
					signer: wallet,
					chainId,
				};
			}

			return {
				type: 'contract',
				address: contractAddress,
				provider,
				chainId,
			};
		} catch (error) {
			throw new Error(`Failed to get contract: ${error instanceof Error ? error.message : 'Unknown error'}`);
		}
	}

	/**
	 * Execute contract transaction
	 */
	private async executeTransaction(
		contract: unknown,
		method: string,
		params: unknown[],
		value?: string,
		gasLimit?: number,
		gasPrice?: string,
	): Promise<string> {
		const contractObj = contract as {
			type: string;
			address: string;
			provider: unknown;
			signer?: unknown;
			chainId: number;
		};

		if (contractObj.type !== 'contract') {
			throw new Error('Invalid contract instance');
		}

		try {
			const { ethers } = await import('ethers');

			// Create contract instance
			// In production, ABI would be provided via envelope or configuration
			// For now, we'll use a minimal approach with ethers.js
			const provider = contractObj.provider as Provider;

			if (!contractObj.signer) {
				throw new Error('Signer not available for transaction execution');
			}

			const signer = contractObj.signer as Signer;

			// Build transaction
			const txRequest: Record<string, unknown> = {
				to: contractObj.address,
				data: this.encodeMethodCall(method, params),
			};

			if (value) {
				txRequest.value = ethers.parseEther(value);
			}

			if (gasLimit) {
				txRequest.gasLimit = gasLimit;
			}

			if (gasPrice) {
				txRequest.gasPrice = gasPrice;
			}

			// Send transaction
			const tx = await signer.sendTransaction(txRequest);
			
			return tx.hash;
		} catch (error) {
			throw new Error(`Failed to execute transaction: ${error instanceof Error ? error.message : 'Unknown error'}`);
		}
	}

	/**
	 * Encode method call (simplified - in production, use proper ABI encoding)
	 */
	private encodeMethodCall(method: string, params: unknown[]): string {
		// This is a simplified implementation
		// In production, you would:
		// 1. Load contract ABI from configuration or envelope
		// 2. Use ethers.js Interface.encodeFunctionData() or similar
		// 3. Properly encode parameters according to their types
		
		// For now, return placeholder (would need ABI to properly encode)
		throw new Error('Method encoding requires contract ABI. Please provide ABI in envelope or configuration.');
	}

	/**
	 * Wait for transaction confirmation
	 */
	private async waitForConfirmation(txHash: string, chainId: number, maxWaitTime = 60000): Promise<boolean> {
		const chainConfig = this.chainConfig.get(chainId);
		if (!chainConfig) {
			return false;
		}

		try {
			const { ethers } = await import('ethers');
			const provider = new ethers.JsonRpcProvider(chainConfig.rpcUrl);

			const receipt = await provider.waitForTransaction(txHash, 1, maxWaitTime);
			return receipt !== null && receipt.status === 1;
		} catch (error) {
			return false;
		}
	}

	/**
	 * Map error to retry class
	 */
	private mapErrorToRetryClass(error: unknown): 'RETRYABLE' | 'NON_RETRYABLE' | 'MANUAL_REVIEW' {
		if (!(error instanceof Error)) {
			return 'MANUAL_REVIEW';
		}

		const message = error.message.toUpperCase();
		const retryableKeywords = ['NETWORK', 'TIMEOUT', 'RATE', 'TEMPORARY', 'CONNECTION'];
		const nonRetryableKeywords = ['INVALID', 'INSUFFICIENT', 'REVERTED', 'VALIDATION'];

		if (retryableKeywords.some((keyword) => message.includes(keyword))) {
			return 'RETRYABLE';
		}

		if (nonRetryableKeywords.some((keyword) => message.includes(keyword))) {
			return 'NON_RETRYABLE';
		}

		return 'MANUAL_REVIEW';
	}
}
