/**
 * Synor Contract Deployer
 *
 * Handles contract compilation and deployment to Synor network.
 */

import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployedContract, TransactionResponse } from "./type-extensions";
import * as path from "path";
import * as fs from "fs";
import chalk from "chalk";

/**
 * Contract deployer for Synor blockchain
 */
export class SynorDeployer {
  private hre: HardhatRuntimeEnvironment;

  constructor(hre: HardhatRuntimeEnvironment) {
    this.hre = hre;
  }

  /**
   * Deploys a contract
   */
  async deploy(
    contractName: string,
    args: any[] = [],
    options: DeployOptions = {}
  ): Promise<DeployedContract> {
    // Get contract factory
    const artifact = await this.hre.artifacts.readArtifact(contractName);

    // Get deployer account
    const accounts = await this.hre.ethers.getSigners();
    const deployer = options.from
      ? accounts.find(a => a.address.toLowerCase() === options.from?.toLowerCase())
      : accounts[0];

    if (!deployer) {
      throw new Error(`Deployer account not found: ${options.from}`);
    }

    console.log(chalk.blue(`\nDeploying ${contractName}...`));
    console.log(chalk.gray(`  From: ${deployer.address}`));
    console.log(chalk.gray(`  Network: ${this.hre.network.name}`));

    // Estimate gas if not provided
    const gasLimit = options.gasLimit || await this.estimateDeployGas(artifact, args);
    console.log(chalk.gray(`  Gas Limit: ${gasLimit}`));

    // Get gas price
    const gasPrice = options.gasPrice || await this.hre.synor.provider.getGasPrice();
    console.log(chalk.gray(`  Gas Price: ${gasPrice} wei`));

    // Encode constructor arguments
    const factory = await this.hre.ethers.getContractFactory(contractName, deployer);
    const deployTx = await factory.getDeployTransaction(...args);

    // Send deployment transaction
    const tx = await this.hre.synor.provider.sendTransaction({
      from: deployer.address,
      data: deployTx.data as string,
      gasLimit: Number(gasLimit),
      gasPrice,
      value: options.value,
    });

    console.log(chalk.yellow(`  Transaction: ${tx.hash}`));

    // Wait for confirmation
    const confirmations = options.confirmations || this.hre.config.synor.confirmations;
    console.log(chalk.gray(`  Waiting for ${confirmations} confirmation(s)...`));

    const receipt = await tx.wait(confirmations);

    if (!receipt.contractAddress) {
      throw new Error("Contract deployment failed - no contract address in receipt");
    }

    console.log(chalk.green(`  ✓ Deployed at: ${receipt.contractAddress}`));

    // Verify bytecode was deployed
    const code = await this.hre.synor.provider.getCode(receipt.contractAddress);
    if (code === "0x" || code === "") {
      throw new Error("Contract deployment failed - no code at address");
    }

    // Create contract instance
    const contract = factory.attach(receipt.contractAddress);

    return {
      address: receipt.contractAddress,
      deployTransaction: tx,
      interface: contract.interface,
      connect: (signer: any) => contract.connect(signer) as any,
    };
  }

  /**
   * Deploys a contract with deterministic address (CREATE2)
   */
  async deployDeterministic(
    contractName: string,
    args: any[] = [],
    salt: string,
    options: DeployOptions = {}
  ): Promise<DeployedContract> {
    // Synor uses a factory contract for CREATE2 deployments
    const factoryAddress = "0x4e59b44847b379578588920cA78FbF26c0B4956C"; // Standard CREATE2 factory

    const artifact = await this.hre.artifacts.readArtifact(contractName);
    const factory = await this.hre.ethers.getContractFactory(contractName);
    const deployTx = await factory.getDeployTransaction(...args);
    const initCode = deployTx.data as string;

    // Compute expected address
    const computedAddress = this.computeCreate2Address(factoryAddress, salt, initCode);
    console.log(chalk.blue(`\nDeterministic deployment of ${contractName}`));
    console.log(chalk.gray(`  Expected address: ${computedAddress}`));

    // Check if already deployed
    const existingCode = await this.hre.synor.provider.getCode(computedAddress);
    if (existingCode !== "0x" && existingCode !== "") {
      console.log(chalk.yellow(`  Contract already deployed at ${computedAddress}`));
      return {
        address: computedAddress,
        deployTransaction: {} as TransactionResponse,
        interface: factory.interface,
        connect: (signer: any) => factory.attach(computedAddress).connect(signer) as any,
      };
    }

    // Deploy via factory
    const accounts = await this.hre.ethers.getSigners();
    const deployer = accounts[0];

    const tx = await this.hre.synor.provider.sendTransaction({
      from: deployer.address,
      to: factoryAddress,
      data: salt + initCode.slice(2), // salt (32 bytes) + init code
      gasLimit: options.gasLimit || 3000000,
    });

    const receipt = await tx.wait(options.confirmations || 1);

    console.log(chalk.green(`  ✓ Deployed at: ${computedAddress}`));

    return {
      address: computedAddress,
      deployTransaction: tx,
      interface: factory.interface,
      connect: (signer: any) => factory.attach(computedAddress).connect(signer) as any,
    };
  }

  /**
   * Computes CREATE2 address
   */
  private computeCreate2Address(factory: string, salt: string, initCode: string): string {
    const { keccak256, concat, getBytes } = require("ethers");

    const initCodeHash = keccak256(initCode);
    const data = concat([
      "0xff",
      factory,
      salt,
      initCodeHash,
    ]);

    return "0x" + keccak256(data).slice(-40);
  }

  /**
   * Estimates gas for deployment
   */
  private async estimateDeployGas(artifact: any, args: any[]): Promise<bigint> {
    const factory = await this.hre.ethers.getContractFactory(artifact.contractName);
    const deployTx = await factory.getDeployTransaction(...args);

    try {
      const gas = await this.hre.synor.provider.estimateGas({
        data: deployTx.data as string,
      });
      // Add 20% buffer
      return gas * BigInt(120) / BigInt(100);
    } catch {
      // Default gas limit
      return BigInt(3000000);
    }
  }

  /**
   * Compiles contracts to WASM for Synor VM
   */
  async compileToWasm(): Promise<void> {
    const artifactsPath = this.hre.config.paths.artifacts;
    const sourcesPath = this.hre.config.paths.sources;
    const wasmOutputPath = path.join(artifactsPath, "wasm");

    // Create output directory
    if (!fs.existsSync(wasmOutputPath)) {
      fs.mkdirSync(wasmOutputPath, { recursive: true });
    }

    console.log(chalk.blue("\nCompiling to WASM..."));

    // For Rust contracts, use cargo
    const contractsDir = path.join(this.hre.config.paths.root, "contracts");
    if (fs.existsSync(contractsDir)) {
      const contracts = fs.readdirSync(contractsDir);

      for (const contract of contracts) {
        const contractPath = path.join(contractsDir, contract);
        const cargoToml = path.join(contractPath, "Cargo.toml");

        if (fs.existsSync(cargoToml)) {
          console.log(chalk.gray(`  Compiling ${contract}...`));
          // In production, this would invoke cargo build
          // await execAsync(`cargo build --manifest-path ${cargoToml} --target wasm32-unknown-unknown --release`);
          console.log(chalk.green(`  ✓ ${contract} compiled`));
        }
      }
    }

    console.log(chalk.green("\nWASM compilation complete!"));
  }

  /**
   * Generates TypeScript types for contracts
   */
  async generateTypes(): Promise<void> {
    const outputDir = path.join(this.hre.config.paths.root, "typechain-types");

    console.log(chalk.blue("\nGenerating TypeScript types..."));

    // Create output directory
    if (!fs.existsSync(outputDir)) {
      fs.mkdirSync(outputDir, { recursive: true });
    }

    // Get all artifacts
    const artifactNames = await this.hre.artifacts.getAllFullyQualifiedNames();

    for (const name of artifactNames) {
      const artifact = await this.hre.artifacts.readArtifact(name);

      if (!artifact.abi || artifact.abi.length === 0) continue;

      const typeName = artifact.contractName;
      const types = this.generateContractTypes(artifact);

      const outputPath = path.join(outputDir, `${typeName}.ts`);
      fs.writeFileSync(outputPath, types);

      console.log(chalk.gray(`  Generated ${typeName}.ts`));
    }

    // Generate index file
    const indexContent = artifactNames
      .map(name => {
        const contractName = name.split(":").pop()!;
        return `export * from "./${contractName}";`;
      })
      .join("\n");

    fs.writeFileSync(path.join(outputDir, "index.ts"), indexContent);

    console.log(chalk.green("\nType generation complete!"));
  }

  /**
   * Generates TypeScript types for a contract
   */
  private generateContractTypes(artifact: any): string {
    const contractName = artifact.contractName;
    const abi = artifact.abi;

    let output = `// Auto-generated types for ${contractName}\n\n`;
    output += `import { ethers } from "ethers";\n\n`;

    // Generate interface for the contract
    output += `export interface ${contractName} extends ethers.Contract {\n`;

    for (const item of abi) {
      if (item.type === "function") {
        const params = item.inputs
          .map((input: any, i: number) => `${input.name || `arg${i}`}: ${this.solidityToTs(input.type)}`)
          .join(", ");

        const returnType = item.outputs && item.outputs.length > 0
          ? this.solidityToTs(item.outputs[0].type)
          : "void";

        output += `  ${item.name}(${params}): Promise<${returnType}>;\n`;
      }
    }

    output += `}\n`;

    return output;
  }

  /**
   * Converts Solidity type to TypeScript type
   */
  private solidityToTs(solType: string): string {
    if (solType.startsWith("uint") || solType.startsWith("int")) {
      return "bigint";
    }
    if (solType === "bool") {
      return "boolean";
    }
    if (solType === "address") {
      return "string";
    }
    if (solType === "string") {
      return "string";
    }
    if (solType.startsWith("bytes")) {
      return "string";
    }
    if (solType.endsWith("[]")) {
      const baseType = solType.slice(0, -2);
      return `${this.solidityToTs(baseType)}[]`;
    }
    return "any";
  }
}

/**
 * Deployment options
 */
export interface DeployOptions {
  /** Account to deploy from */
  from?: string;
  /** Gas limit */
  gasLimit?: number;
  /** Gas price in wei */
  gasPrice?: bigint;
  /** Value to send with deployment */
  value?: bigint;
  /** Number of confirmations to wait */
  confirmations?: number;
}
