import { Injectable, Logger } from '@nestjs/common';
import { ethers } from 'ethers';
import { Web3Service } from './web3.service';
import { LicenseMetadata, LicenseVerification } from '../interfaces';

@Injectable()
export class LicenseNFTService {
  private readonly logger = new Logger(LicenseNFTService.name);

  // Simple ERC721 ABI for mint and verify functions
  private readonly licenseNFTAbi = [
    'function mint(address to, string calldata requestId, string calldata metadataUri) public returns (uint256)',
    'function tokenOfRequest(string calldata requestId) public view returns (uint256)',
    'function exists(uint256 tokenId) public view returns (bool)',
    'function ownerOf(uint256 tokenId) public view returns (address)',
    'function revoke(uint256 tokenId) public',
    'function isRevoked(uint256 tokenId) public view returns (bool)',
    'function getMetadata(uint256 tokenId) public view returns (string)',
  ];

  constructor(private web3Service: Web3Service) {}

  async mintLicense(
    contractAddress: string,
    requestId: string,
    applicantAddress: string,
    metadata: LicenseMetadata,
  ): Promise<{ tokenId: bigint; transactionHash: string }> {
    try {
      this.logger.debug(`Minting NFT for request: ${requestId}`);

      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;

      // Encode metadata as URI
      const metadataUri = JSON.stringify(metadata);

      const tx = await contract.mint(applicantAddress, requestId, metadataUri);

      this.logger.debug(`Mint transaction sent: ${tx.hash}`);

      // Wait for confirmation
      const receipt = await this.web3Service.sendTransaction(tx);

      // Extract token ID from transaction receipt logs
      const interface_ = new ethers.Interface(this.licenseNFTAbi);
      let tokenId: bigint | null = null;

      // Look for Transfer event to get token ID
      for (const log of receipt.logs || []) {
        try {
          const parsed = interface_.parseLog(log as any);
          if (parsed?.name === 'Transfer') {
            tokenId = parsed.args[2] as bigint; // tokenId is third argument in Transfer event
            break;
          }
        } catch (e) {
          // Continue if log parsing fails
        }
      }

      if (!tokenId) {
        throw new Error('Failed to extract token ID from transaction receipt');
      }

      this.logger.log(`NFT minted successfully: tokenId=${tokenId}, txHash=${receipt.hash}`);

      return {
        tokenId,
        transactionHash: receipt.hash!,
      };
    } catch (error) {
      this.logger.error(`Failed to mint NFT for request ${requestId}`, error);
      throw error;
    }
  }

  async getLicenseMetadata(contractAddress: string, tokenId: bigint): Promise<LicenseMetadata> {
    try {
      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;

      const metadataUri = await contract.getMetadata(tokenId);
      const metadata = JSON.parse(metadataUri);

      return metadata;
    } catch (error) {
      this.logger.error(`Failed to get license metadata for tokenId ${tokenId}`, error);
      throw error;
    }
  }

  async isLicenseValid(contractAddress: string, tokenId: bigint): Promise<boolean> {
    try {
      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;

      const exists = await contract.exists(tokenId);
      if (!exists) {
        return false;
      }

      const isRevoked = await contract.isRevoked(tokenId);
      return !isRevoked;
    } catch (error) {
      this.logger.error(`Failed to validate license ${tokenId}`, error);
      return false;
    }
  }

  async revokeLicense(contractAddress: string, tokenId: bigint): Promise<string> {
    try {
      this.logger.debug(`Revoking license: ${tokenId}`);

      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;

      const tx = await contract.revoke(tokenId);
      const receipt = await this.web3Service.sendTransaction(tx);

      this.logger.log(`License revoked: ${tokenId}, txHash=${receipt.hash}`);
      return receipt.hash!;
    } catch (error) {
      this.logger.error(`Failed to revoke license ${tokenId}`, error);
      throw error;
    }
  }

  async getOwner(contractAddress: string, tokenId: bigint): Promise<string> {
    try {
      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;
      return await contract.ownerOf(tokenId);
    } catch (error) {
      this.logger.error(`Failed to get owner for tokenId ${tokenId}`, error);
      throw error;
    }
  }

  async getTokenIdByRequest(contractAddress: string, requestId: string): Promise<bigint | null> {
    try {
      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;
      return await contract.tokenOfRequest(requestId);
    } catch (error) {
      this.logger.warn(`No token found for request ${requestId}`);
      return null;
    }
  }

  async verifyLicense(contractAddress: string, tokenId: bigint): Promise<LicenseVerification> {
    try {
      const isValid = await this.isLicenseValid(contractAddress, tokenId);
      const metadata = await this.getLicenseMetadata(contractAddress, tokenId);
      const owner = await this.getOwner(contractAddress, tokenId);
      const contract = this.web3Service.getContract(contractAddress, this.licenseNFTAbi) as any;
      const isRevoked = await contract.isRevoked(tokenId);

      return {
        isValid: isValid && !isRevoked,
        tokenId,
        metadata,
        isRevoked,
        owner,
      };
    } catch (error) {
      this.logger.error(`Failed to verify license ${tokenId}`, error);
      throw error;
    }
  }
}
