import { BaseZonefileData, PriceFunction, ZonefileData } from "./interfaces";
import { ClarityValue, UIntCV, ListCV } from "@stacks/transactions";
import {
  makeRandomPrivKey,
  getAddressFromPrivateKey,
} from "@stacks/transactions";
import { CallbackFunction } from "./config";

function hasNoQueryOrFragment(urlString: string): boolean {
  const url = new URL(urlString);
  return !url.search && !url.hash;
}

function noUserInfo(urlString: string): boolean {
  const url = new URL(urlString);
  return !url.username && !url.password;
}

function isAllowedS3Domain(urlString: string): boolean {
  try {
    const url = new URL(urlString);
    const s3DomainPattern =
      /^[a-z0-9.-]+\.s3([.-][a-z0-9-]+)*\.amazonaws\.com$/i;
    return s3DomainPattern.test(url.hostname);
  } catch {
    return false;
  }
}

function isValidHttpsUrl(urlString: string): boolean {
  try {
    const url = new URL(urlString);
    return url.protocol === "https:";
  } catch {
    return false;
  }
}

function hasJsonExtension(urlString: string): boolean {
  const pathname = new URL(urlString).pathname.toLowerCase();
  return pathname.endsWith(".json");
}

function isSafeDomain(urlString: string): boolean {
  const url = new URL(urlString);
  const forbiddenPatterns = [/^localhost$/, /^127\.0\.0\.1$/];
  return !forbiddenPatterns.some((pattern) => pattern.test(url.hostname));
}

export function decodeFQN(fqdn: string): {
  name: string;
  namespace: string;
  subdomain?: string;
} {
  const nameParts = fqdn.split(".");
  if (nameParts.length > 2) {
    return {
      subdomain: nameParts[0],
      name: nameParts[1],
      namespace: nameParts[2],
    };
  }
  return {
    name: nameParts[0],
    namespace: nameParts[1],
  };
}

export function parsePriceFunction(data: {
  [key: string]: ClarityValue;
}): PriceFunction {
  const buckets = (data["buckets"] as ListCV<UIntCV>).value;
  return {
    base: (data["base"] as UIntCV).value,
    coefficient: (data["coeff"] as UIntCV).value,
    b1: buckets[0].value,
    b2: buckets[1].value,
    b3: buckets[2].value,
    b4: buckets[3].value,
    b5: buckets[4].value,
    b6: buckets[5].value,
    b7: buckets[6].value,
    b8: buckets[7].value,
    b9: buckets[8].value,
    b10: buckets[9].value,
    b11: buckets[10].value,
    b12: buckets[11].value,
    b13: buckets[12].value,
    b14: buckets[13].value,
    b15: buckets[14].value,
    b16: buckets[15].value,
    nonAlphaDiscount: (data["nonalpha-discount"] as UIntCV).value,
    noVowelDiscount: (data["no-vowel-discount"] as UIntCV).value,
  };
}

export function asciiToUtf8(asciiCodes: string): string {
  return asciiCodes
    .split(",")
    .map((code) => String.fromCharCode(parseInt(code.trim())))
    .join("");
}

export function generateRandomAddress() {
  const randomPrivateKey = makeRandomPrivKey();
  const privateKeyString = randomPrivateKey;
  const randomAddress = getAddressFromPrivateKey(privateKeyString);

  return randomAddress;
}

export function parseZonefile(zonefileString: string): ZonefileData {
  try {
    const parsed = JSON.parse(zonefileString);
    const baseData: BaseZonefileData = {
      owner: parsed.owner || "",
      general: parsed.general || "",
      twitter: parsed.twitter || "",
      url: parsed.url || "",
      nostr: parsed.nostr || "",
      lightning: parsed.lightning || "",
      btc: parsed.btc || "",
    };

    if (parsed.externalSubdomainFile) {
      return {
        ...baseData,
        externalSubdomainFile: parsed.externalSubdomainFile,
      } as ZonefileData;
    }

    return {
      ...baseData,
      subdomains: parsed.subdomains || {},
    } as ZonefileData;
  } catch (error) {
    console.error("Error parsing zonefile:", error);
    return {
      owner: "",
      general: "",
      twitter: "",
      url: "",
      nostr: "",
      lightning: "",
      btc: "",
      subdomains: {},
    } as ZonefileData;
  }
}

export function createZonefileData(params: ZonefileData): ZonefileData {
  const baseData: BaseZonefileData = {
    owner: params.owner,
    general: params.general || "",
    twitter: params.twitter || "",
    url: params.url || "",
    nostr: params.nostr || "",
    lightning: params.lightning || "",
    btc: params.btc || "",
  };

  if ("externalSubdomainFile" in params && params.externalSubdomainFile) {
    const fileUrl = params.externalSubdomainFile;
    if (
      !isValidHttpsUrl(fileUrl) ||
      !hasJsonExtension(fileUrl) ||
      !isSafeDomain(fileUrl) ||
      !isAllowedS3Domain(fileUrl) ||
      !hasNoQueryOrFragment(fileUrl) ||
      !noUserInfo(fileUrl)
    ) {
      throw new Error("Invalid externalSubdomainFile URL");
    }

    return {
      ...baseData,
      externalSubdomainFile: fileUrl,
    } as ZonefileData;
  }

  return {
    ...baseData,
    subdomains: "subdomains" in params ? params.subdomains : {},
  } as ZonefileData;
}

export function stringifyZonefile(zonefileData: ZonefileData): string {
  return JSON.stringify(zonefileData);
}

export function addCallbacks<T>(
  options: T,
  onFinish?: CallbackFunction,
  onCancel?: CallbackFunction
): T & { onFinish?: CallbackFunction; onCancel?: CallbackFunction } {
  return { ...options, onFinish, onCancel };
}
