import { ethers } from 'ethers';
import type {
  Address, Bytes32, Hex, V3DeploymentName, V3NetworkProfile,
} from '../types';

export interface RuntimeDeploymentVerifier {
  getVerifiedChainId(): Promise<bigint>;
  getAbiIdentity(address: Address): Promise<Bytes32 | null>;
  /** Returns the exact deployed runtime bytecode at address; `0x` and null mean no code. */
  getRuntimeCode(address: Address): Promise<Hex | null>;
}

const validAddress = (value: unknown): value is Address =>
  typeof value === 'string' && ethers.isAddress(value) && value !== ethers.ZeroAddress;
const validBytes32 = (value: unknown): value is Bytes32 =>
  typeof value === 'string' && /^0x[0-9a-fA-F]{64}$/.test(value);
const validRuntimeCode = (value: unknown): value is Hex =>
  typeof value === 'string' && /^0x(?:[0-9a-fA-F]{2})+$/.test(value);
const sameHex = (left: string, right: string): boolean =>
  left.toLowerCase() === right.toLowerCase();

/**
 * Verifies a deployment against SDK-owned authorization and independently
 * observed runtime code. Proxies are intentionally unsupported: the exact code
 * at the configured address must hash to the pinned runtime hash.
 */
export async function verifyAuthorizedDeployment(
  profile: V3NetworkProfile,
  canonicalProfile: V3NetworkProfile,
  name: V3DeploymentName,
  canonicalAbiIdentity: Bytes32,
  verifier: RuntimeDeploymentVerifier,
): Promise<Address> {
  const configured = profile.deployments[name];
  const authorized = canonicalProfile.deployments[name];
  if (canonicalProfile.status !== 'configured' ||
      profile.chainId !== canonicalProfile.chainId ||
      !validAddress(configured.address) ||
      !validAddress(authorized.address) ||
      !sameHex(configured.address, authorized.address) ||
      !validBytes32(configured.abiIdentity) ||
      !validBytes32(authorized.abiIdentity) ||
      !sameHex(configured.abiIdentity, canonicalAbiIdentity) ||
      !sameHex(authorized.abiIdentity, canonicalAbiIdentity) ||
      !validBytes32(configured.runtimeCodeHash) ||
      !validBytes32(authorized.runtimeCodeHash) ||
      !sameHex(configured.runtimeCodeHash, authorized.runtimeCodeHash)) {
    throw new TypeError(`deployment ${name} is not canonically authorized`);
  }

  const observedChainId = await verifier.getVerifiedChainId();
  if (observedChainId !== BigInt(canonicalProfile.chainId)) {
    throw new TypeError(`verified chain does not match deployment ${name}`);
  }

  const observedIdentity = await verifier.getAbiIdentity(authorized.address);
  if (!validBytes32(observedIdentity) || !sameHex(observedIdentity, canonicalAbiIdentity)) {
    throw new TypeError(`verified ABI identity does not match deployment ${name}`);
  }

  const runtimeCode = await verifier.getRuntimeCode(authorized.address);
  if (!validRuntimeCode(runtimeCode)) {
    throw new TypeError(`deployment ${name} has no valid runtime bytecode`);
  }
  const observedRuntimeCodeHash = ethers.keccak256(runtimeCode) as Bytes32;
  if (!sameHex(observedRuntimeCodeHash, authorized.runtimeCodeHash)) {
    throw new TypeError(`verified runtime code hash does not match deployment ${name}`);
  }
  return authorized.address;
}
