import { ScriptType } from "../types";

import {
  P2PKH_INPUT_SIZE,
  P2PKH_OUTPUT_SIZE,
  P2SH_OUTPUT_SIZE,
  P2SH_P2WPKH_INPUT_SIZE,
  P2WPKH_INPUT_SIZE,
  P2WSH_OUTPUT_SIZE,
  P2TR_OUTPUT_SIZE,
  P2TR_INPUT_SIZE,
  P2TR_INSCRIPTION_INPUT_SIZE,
  SIGNATURE_SIZE,
  MULTISIG_REDEEM_SCRIPT_SIZE,
  OUTPOINT_SIZE,
  SEQUENCE_SIZE,
  P2WPKH_OUTPUT_SIZE,
  P2SH_P2WPKH_OUTPUT_SIZE,
} from "../constants";
import { encodingLength } from "varuint-bitcoin";
import { bech32ConvertWords, bech32Decode } from "../utils/bech32";
import { bs58Decode } from "../utils/bs58";

export function getScriptLengthSize(length: number): number {
  if (length < 75) return 1;
  if (length <= 255) return 2;
  if (length <= 65535) return 3;
  if (length <= 4294967295) return 5;
  throw new Error("Script size too large");
}

export function getScriptTypeFromAddress(address: string): ScriptType {
  if (!address || typeof address !== "string") {
    return ScriptType.UNKNOWN;
  }

  const addressLower = address.toLowerCase();

  if (addressLower.startsWith("bc1") || addressLower.startsWith("tb1")) {
    try {
      const decoded = bech32Decode(addressLower);
      if (!decoded) return ScriptType.UNKNOWN;

      const data = bech32ConvertWords(decoded.words, 5, 8, false);
      const version = decoded.words[0];

      if (
        decoded.encoding === "bech32m" &&
        version === 1 &&
        data.length === 33
      ) {
        return ScriptType.P2TR;
      }
      if (decoded.encoding === "bech32" && version === 0) {
        if (data.length === 20) return ScriptType.P2WPKH;
        if (data.length === 33) return ScriptType.P2WSH;
      }
    } catch {
      return ScriptType.UNKNOWN;
    }
    return ScriptType.UNKNOWN;
  }

  try {
    const decoded = bs58Decode(address);
    const version = decoded[0];

    if (version === 0x00 || version === 0x6f) return ScriptType.P2PKH;
    if (version === 0x05 || version === 0xc4) return ScriptType.P2SH;
    return ScriptType.UNKNOWN;
  } catch {
    return ScriptType.UNKNOWN;
  }
}

export const detectOutputScriptType = (
  scriptPubKey: Buffer,
  redeemScript?: string | Buffer,
): ScriptType => {
  if (!scriptPubKey || scriptPubKey.length === 0) {
    return ScriptType.UNKNOWN;
  }

  const scriptLength = scriptPubKey.length;

  // OP_RETURN
  if (scriptPubKey[0] === 0x6a) {
    return ScriptType.OP_RETURN;
  }

  // P2PK (compressed or uncompressed)
  if (
    scriptLength === 35 ||
    (scriptLength === 67 && scriptPubKey[scriptLength - 1] === 0xac)
  ) {
    return ScriptType.P2PK;
  }

  // P2PKH
  if (scriptLength === 25) {
    if (
      scriptPubKey[0] === 0x76 && // OP_DUP
      scriptPubKey[1] === 0xa9 && // OP_HASH160
      scriptPubKey[2] === 0x14 && // OP_PUSH20
      scriptPubKey[23] === 0x88 && // OP_EQUALVERIFY
      scriptPubKey[24] === 0xac // OP_CHECKSIG
    ) {
      return ScriptType.P2PKH;
    }
    return ScriptType.UNKNOWN;
  }

  // P2SH
  if (scriptLength === 23) {
    if (
      scriptPubKey[0] === 0xa9 && // OP_HASH160
      scriptPubKey[1] === 0x14 && // OP_PUSH20
      scriptPubKey[22] === 0x87 // OP_EQUAL
    ) {
      if (redeemScript) {
        const redeemBuffer =
          typeof redeemScript === "string"
            ? Buffer.from(redeemScript, "hex")
            : redeemScript;

        // redeem script: P2WPKH
        if (
          redeemBuffer.length === 22 &&
          redeemBuffer[0] === 0x00 &&
          redeemBuffer[1] === 0x14
        ) {
          return ScriptType.P2SH_P2WPKH;
        }

        // redeem script: P2WSH)
        if (
          redeemBuffer.length === 34 &&
          redeemBuffer[0] === 0x00 &&
          redeemBuffer[1] === 0x20
        ) {
          return ScriptType.P2SH_P2WSH;
        }
      }
      return ScriptType.P2SH;
    }
    return ScriptType.UNKNOWN;
  }

  // P2WPKH
  if (scriptLength === 22) {
    if (
      scriptPubKey[0] === 0x00 && // OP_0
      scriptPubKey[1] === 0x14 // OP_PUSH20
    ) {
      return ScriptType.P2WPKH;
    }
    return ScriptType.UNKNOWN;
  }

  // P2TR
  if (
    scriptLength === 34 &&
    scriptPubKey[0] === 0x51 && // OP_1
    scriptPubKey[1] === 0x20 // OP_PUSH32
  ) {
    return ScriptType.P2TR;
  }

  // P2WSH
  if (scriptLength === 34) {
    if (
      scriptPubKey[0] === 0x00 && // OP_0
      scriptPubKey[1] === 0x20 // OP_PUSH32
    ) {
      return ScriptType.P2WSH;
    }
  }
  return ScriptType.UNKNOWN;
};

export const estimateInputSize = (inputScriptType: ScriptType): number => {
  switch (inputScriptType) {
    case "P2PKH":
      return P2PKH_INPUT_SIZE;
    case "P2SH-P2WPKH":
      return P2SH_P2WPKH_INPUT_SIZE;
    case "P2WPKH":
      return P2WPKH_INPUT_SIZE;
    case "P2TR":
      return P2TR_INPUT_SIZE;
    case "P2TR-INSCRIPTION":
      return P2TR_INSCRIPTION_INPUT_SIZE;
    case "P2SH": {
      const scriptSigSize =
        1 + // OP_0
        1 * (1 + SIGNATURE_SIZE) + // Signature with length
        getScriptLengthSize(MULTISIG_REDEEM_SCRIPT_SIZE) +
        MULTISIG_REDEEM_SCRIPT_SIZE;
      return (
        OUTPOINT_SIZE +
        encodingLength(scriptSigSize) +
        scriptSigSize +
        SEQUENCE_SIZE
      );
    }
    case "P2SH-P2WSH":
      return P2SH_P2WPKH_INPUT_SIZE;
    case "P2WSH":
      return OUTPOINT_SIZE + SEQUENCE_SIZE;
    default:
      throw new Error(`Unsupported script type: ${inputScriptType}`);
  }
};

export const getOutputSize = (outputScript: ScriptType): number => {
  switch (outputScript) {
    case "P2PKH":
      return P2PKH_OUTPUT_SIZE;
    case "P2SH":
      return P2SH_OUTPUT_SIZE;
    case "P2SH-P2WPKH":
      return P2SH_P2WPKH_OUTPUT_SIZE;
    case "P2SH-P2WSH":
      return P2SH_OUTPUT_SIZE;
    case "P2WPKH":
      return P2WPKH_OUTPUT_SIZE;
    case "P2TR":
      return P2TR_OUTPUT_SIZE;
    case "P2WSH":
      return P2WSH_OUTPUT_SIZE;
    case "OP_RETURN":
      return 0;
    default:
      return 67; // safe value
  }
};
