// ═══════════════════════════════════════════════════════════════
// Blockchain Service — Server-Side Smart Contract Integration
// ═══════════════════════════════════════════════════════════════
//
// This service handles ALL interactions with the Ethereum blockchain
// from the Next.js server (server actions / route handlers).
//
// KEY DESIGN DECISIONS:
// ─────────────────────
// 1. SERVER-SIDE ONLY: Uses ethers.js JsonRpcProvider + Wallet
//    (NOT BrowserProvider / MetaMask). Users don't need a wallet.
//
// 2. DUAL-NETWORK: Automatically connects to Hardhat (local dev)
//    or Sepolia (demo/production) based on env vars.
//
// 3. DOCUMENT HASHING: Computes SHA-256 of the contract PDF file
//    content, then converts to bytes32 for Solidity compatibility.
//
// FLOW:
//   Upload PDF → Download → SHA-256 Hash → Send to Smart Contract
//   → Store txHash + blockNumber in PostgreSQL
// ═══════════════════════════════════════════════════════════════

import { ethers } from "ethers";
import { createHash } from "crypto";
import type {
  BlockchainProof,
  BlockchainVerification,
  BlockchainStats,
} from "./blockchain.types";

// ─────────────────────────────────────────────────
// Smart Contract ABI (Application Binary Interface)
// ─────────────────────────────────────────────────
// This is the "API definition" of our Solidity contract.
// It tells ethers.js what functions exist and their signatures.
// Generated by `npx hardhat compile` from DocumentRegistry.sol
const DOCUMENT_REGISTRY_ABI = [
  "function registerDocument(bytes32 _docHash) external",
  "function verifyDocument(bytes32 _docHash) external view returns (bool exists, uint256 timestamp, address depositor)",
  "function getTimestamp(bytes32 _docHash) external view returns (uint256)",
  "function getDocumentsByDepositor(address _depositor) external view returns (bytes32[] memory)",
  "function getDocumentCount(address _depositor) external view returns (uint256)",
  "function totalDocuments() external view returns (uint256)",
  "function owner() external view returns (address)",
  "event DocumentRegistered(bytes32 indexed docHash, uint256 timestamp, address indexed depositor)",
];

// ─────────────────────────────────────────────────
// CONFIGURATION
// ─────────────────────────────────────────────────

function getConfig() {
  const network = process.env.BLOCKCHAIN_NETWORK || "hardhat";
  const rpcUrl = process.env.BLOCKCHAIN_RPC_URL || "http://127.0.0.1:8545";
  const contractAddress = process.env.BLOCKCHAIN_CONTRACT_ADDRESS || "";
  const privateKey = process.env.BLOCKCHAIN_PRIVATE_KEY || "";

  return { network, rpcUrl, contractAddress, privateKey };
}

function isBlockchainReadConfigured(): boolean {
  const config = getConfig();
  return !!(config.contractAddress && config.rpcUrl);
}

function isBlockchainWriteConfigured(): boolean {
  const config = getConfig();
  return !!(config.contractAddress && config.privateKey && config.rpcUrl);
}

// ─────────────────────────────────────────────────
// PROVIDER & WALLET INSTANCES (lazy singletons)
// ─────────────────────────────────────────────────

let _provider: ethers.JsonRpcProvider | null = null;
let _wallet: ethers.Wallet | null = null;
let _readContract: ethers.Contract | null = null;
let _writeContract: ethers.Contract | null = null;

function getProvider(): ethers.JsonRpcProvider {
  if (!_provider) {
    const { rpcUrl } = getConfig();
    _provider = new ethers.JsonRpcProvider(rpcUrl);
  }
  return _provider;
}

function getWallet(): ethers.Wallet {
  if (!_wallet) {
    const { privateKey } = getConfig();
    if (!privateKey) {
      throw new Error("BLOCKCHAIN_PRIVATE_KEY not configured");
    }
    _wallet = new ethers.Wallet(privateKey, getProvider());
  }
  return _wallet;
}

function getReadContract(): ethers.Contract {
  if (!_readContract) {
    const { contractAddress } = getConfig();
    if (!contractAddress) {
      throw new Error("BLOCKCHAIN_CONTRACT_ADDRESS not configured");
    }
    _readContract = new ethers.Contract(
      contractAddress,
      DOCUMENT_REGISTRY_ABI,
      getProvider(),
    );
  }
  return _readContract;
}

function getWriteContract(): ethers.Contract {
  if (!_writeContract) {
    const { contractAddress } = getConfig();
    if (!contractAddress) {
      throw new Error("BLOCKCHAIN_CONTRACT_ADDRESS not configured");
    }
    _writeContract = new ethers.Contract(
      contractAddress,
      DOCUMENT_REGISTRY_ABI,
      getWallet(),
    );
  }
  return _writeContract;
}

// Reset singletons (useful when env changes at runtime)
function resetInstances() {
  _provider = null;
  _wallet = null;
  _readContract = null;
  _writeContract = null;
}

// ═══════════════════════════════════════════════════════════════
// CORE SERVICE CLASS
// ═══════════════════════════════════════════════════════════════

export class BlockchainService {
  /**
   * Check if blockchain is properly configured.
   * Returns false if env vars are missing — blockchain features
   * are gracefully disabled without breaking the rest of the app.
   */
  static isConfigured(): boolean {
    return isBlockchainWriteConfigured();
  }

  /**
   * Check if blockchain read operations are configured.
   * Read operations (stats/verify) do not require private key.
   */
  static isReadConfigured(): boolean {
    return isBlockchainReadConfigured();
  }

  /**
   * Compute SHA-256 hash of a document from its URL.
   *
   * HOW IT WORKS:
   * 1. Downloads the PDF file from UploadThing URL
   * 2. Computes SHA-256 hash of the raw bytes
   * 3. Prepends "0x" and pads to bytes32 for Solidity
   *
   * WHY SHA-256:
   * - Industry standard for document fingerprinting
   * - Collision resistant (practically impossible for two different
   *   documents to produce the same hash)
   * - The hash is deterministic: same file → same hash, always
   *
   * @param fileUrl - The URL of the document to hash
   * @returns The SHA-256 hash as a 0x-prefixed hex string (bytes32)
   */
  static async hashDocument(fileUrl: string): Promise<string> {
    console.log("🔐 Computing document hash...");

    // Download the file
    const response = await fetch(fileUrl);
    if (!response.ok) {
      throw new Error(`Failed to download file: ${response.statusText}`);
    }

    const arrayBuffer = await response.arrayBuffer();
    const buffer = Buffer.from(arrayBuffer);

    // Compute SHA-256 hash
    const hash = createHash("sha256").update(buffer).digest("hex");

    // Convert to bytes32 format (0x-prefixed, 64 hex characters)
    const bytes32Hash = "0x" + hash;

    console.log(`✅ Document hash: ${bytes32Hash.slice(0, 18)}...`);
    return bytes32Hash;
  }

  /**
   * Register a document hash on the blockchain.
   *
   * HOW IT WORKS:
   * 1. Creates a transaction calling registerDocument() on the smart contract
   * 2. The server wallet signs the transaction with its private key
   * 3. The transaction is broadcast to the Ethereum network
   * 4. We wait for the transaction to be mined (included in a block)
   * 5. The block number and timestamp become the proof
   *
   * WHAT GETS STORED ON-CHAIN:
   * - The document hash (bytes32)
   * - The block timestamp (when the tx was mined)
   * - The depositor address (our server wallet)
   * - The file name (for reference)
   *
   * @param documentHash - SHA-256 hash of the document (bytes32)
   * @param fileName - Original file name
   * @returns Proof data including txHash, block info
   */
  static async registerOnChain(
    documentHash: string,
    fileName: string,
  ): Promise<BlockchainProof> {
    if (!this.isConfigured()) {
      throw new Error("Blockchain not configured. Check your .env variables.");
    }

    console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
    console.log("🔗 Registering document on blockchain...");
    console.log(`📄 File: ${fileName}`);
    console.log(`🔐 Hash: ${documentHash.slice(0, 18)}...`);
    console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

    const contract = getWriteContract();
    const config = getConfig();

    try {
      // Send the transaction to the smart contract (fileName is omitted on-chain)
      const tx = await contract.registerDocument(documentHash);
      console.log(`📤 Transaction sent: ${tx.hash}`);

      // Wait for the transaction to be mined
      const receipt = await tx.wait();
      console.log(`✅ Transaction mined in block #${receipt.blockNumber}`);

      // Get the block to extract the timestamp
      const block = await getProvider().getBlock(receipt.blockNumber);
      const blockTimestamp = block
        ? new Date(block.timestamp * 1000)
        : new Date();

      // Build explorer URL for Sepolia
      const explorerUrl =
        config.network === "sepolia"
          ? `https://sepolia.etherscan.io/tx/${tx.hash}`
          : null;

      const proof: BlockchainProof = {
        documentHash,
        txHash: tx.hash,
        blockNumber: receipt.blockNumber,
        blockTimestamp,
        network: config.network as "hardhat" | "sepolia",
        contractAddress: config.contractAddress,
        explorerUrl,
      };

      console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
      console.log("✅ Document registered on-chain!");
      console.log(`   Block: #${proof.blockNumber}`);
      console.log(`   Time:  ${proof.blockTimestamp.toISOString()}`);
      console.log(`   Tx:    ${proof.txHash}`);
      if (explorerUrl) {
        console.log(`   View:  ${explorerUrl}`);
      }
      console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");

      return proof;
    } catch (error: unknown) {
      // Check if the document was already registered
      const errorMessage =
        error instanceof Error ? error.message : String(error);
      if (errorMessage.includes("already registered")) {
        console.log("ℹ️ Document already registered on-chain (idempotent)");

        // Retrieve existing proof data
        const verification = await this.verifyOnChain(documentHash);
        return {
          documentHash,
          txHash: "already-registered",
          blockNumber: 0,
          blockTimestamp: new Date(verification.timestamp * 1000),
          network: config.network as "hardhat" | "sepolia",
          contractAddress: config.contractAddress,
          explorerUrl: null,
        };
      }

      console.error("❌ Blockchain registration failed:", errorMessage);
      throw new Error(`Blockchain registration failed: ${errorMessage}`);
    }
  }

  /**
   * Verify if a document exists on the blockchain.
   *
   * This is a READ-ONLY operation (no gas cost, no transaction).
   * It queries the smart contract's state to check if a hash
   * was previously registered.
   *
   * @param documentHash - The SHA-256 hash to verify
   * @returns Verification result with existence, timestamp, depositor
   */
  static async verifyOnChain(
    documentHash: string,
  ): Promise<BlockchainVerification> {
    if (!this.isReadConfigured()) {
      throw new Error("Blockchain read access not configured");
    }

    const contract = getReadContract();

    const [exists, timestamp, depositor] =
      await contract.verifyDocument(documentHash);

    return {
      exists: exists as boolean,
      timestamp: Number(timestamp),
      depositor: depositor as string,
    };
  }

  /**
   * Hash a document AND register it on-chain in one step.
   * This is the main entry point used by the contract analysis flow.
   */
  static async hashAndRegister(
    fileUrl: string,
    fileName: string,
  ): Promise<BlockchainProof> {
    const documentHash = await this.hashDocument(fileUrl);
    return await this.registerOnChain(documentHash, fileName);
  }

  /**
   * Get blockchain network stats for the explorer page header.
   */
  static async getNetworkStats(): Promise<BlockchainStats> {
    if (!this.isReadConfigured()) {
      return {
        totalVerified: 0,
        latestBlockNumber: null,
        networkName: "Not Configured",
        networkStatus: "disconnected",
        walletAddress: "",
      };
    }

    try {
      const provider = getProvider();
      const contract = getReadContract();
      const config = getConfig();
      let walletAddress = "";

      if (isBlockchainWriteConfigured()) {
        try {
          walletAddress = getWallet().address;
        } catch {
          walletAddress = "";
        }
      }

      const [blockNumber, totalDocs, networkObj] = await Promise.all([
        provider.getBlockNumber(),
        contract.totalDocuments(),
        provider.getNetwork(),
      ]);

      return {
        totalVerified: Number(totalDocs),
        latestBlockNumber: blockNumber,
        networkName:
          config.network === "sepolia" ? "Ethereum Sepolia" : "Hardhat Local",
        networkStatus: "connected",
        walletAddress,
        chainId: Number(networkObj.chainId),
      };
    } catch {
      const config = getConfig();
      return {
        totalVerified: 0,
        latestBlockNumber: null,
        networkName:
          config.network === "sepolia" ? "Ethereum Sepolia" : "Hardhat Local",
        networkStatus: "disconnected",
        walletAddress: "",
      };
    }
  }

  /**
   * Reset cached provider/wallet connections.
   * Useful if env vars change during development.
   */
  static resetConnections() {
    resetInstances();
  }
}
