import {
  AddressEntry,
  BaseZonefileData,
  MetaEntry,
  NewZonefileData,
  PriceFunction,
  SocialEntry,
  SubdomainEntry,
  SubdomainProperties,
  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 || "",
    };

    // Accept both key names for backward compat
    const externalFile = parsed.externalSubdomainsFile ?? parsed.externalSubdomainFile;
    if (externalFile) {
      return {
        ...baseData,
        externalSubdomainsFile: externalFile,
      } as ZonefileData;
    }

    return {
      ...baseData,
      subdomains: Array.isArray(parsed.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 ("externalSubdomainsFile" in params && params.externalSubdomainsFile) {
    const fileUrl = params.externalSubdomainsFile;
    if (
      !isValidHttpsUrl(fileUrl) ||
      !hasJsonExtension(fileUrl) ||
      !isSafeDomain(fileUrl) ||
      !isAllowedS3Domain(fileUrl) ||
      !hasNoQueryOrFragment(fileUrl) ||
      !noUserInfo(fileUrl)
    ) {
      throw new Error("Invalid externalSubdomainsFile URL");
    }

    return {
      ...baseData,
      externalSubdomainsFile: 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 };
}

function isValidImageUrl(url: string): boolean {
  try {
    const validExtensions = [".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"];
    const urlObj = new URL(url);
    const pathname = urlObj.pathname.toLowerCase();
    return validExtensions.some((ext) => pathname.endsWith(ext));
  } catch {
    return false;
  }
}

function isValidUrl(url: string): boolean {
  try {
    new URL(url);
    return true;
  } catch {
    return false;
  }
}

function validateSocialEntry(social: SocialEntry): boolean {
  return (
    typeof social.platform === "string" &&
    typeof social.username === "string" &&
    social.platform.trim() !== "" &&
    social.username.trim() !== ""
  );
}

function validateAddressEntry(address: AddressEntry): boolean {
  return (
    typeof address.network === "string" &&
    typeof address.address === "string" &&
    typeof address.type === "string" &&
    address.network.trim() !== "" &&
    address.address.trim() !== "" &&
    address.type.trim() !== ""
  );
}

function validateMetaEntry(meta: MetaEntry): boolean {
  return (
    typeof meta.name === "string" &&
    typeof meta.value === "string" &&
    meta.name.trim() !== ""
  );
}

function validateSubdomainEntry(subdomain: SubdomainProperties): boolean {
  if (
    !subdomain.owner ||
    typeof subdomain.owner !== "string" ||
    subdomain.owner.trim() === ""
  ) {
    return false;
  }

  if (
    subdomain.pfp &&
    (!isValidUrl(subdomain.pfp) || !isValidImageUrl(subdomain.pfp))
  ) {
    return false;
  }

  if (subdomain.website && !isValidUrl(subdomain.website)) {
    return false;
  }

  if (subdomain.social && !Array.isArray(subdomain.social)) {
    return false;
  }

  if (subdomain.social) {
    for (const social of subdomain.social) {
      if (!validateSocialEntry(social)) {
        return false;
      }
    }
  }

  if (subdomain.addresses && !Array.isArray(subdomain.addresses)) {
    return false;
  }

  if (subdomain.addresses) {
    for (const address of subdomain.addresses) {
      if (!validateAddressEntry(address)) {
        return false;
      }
    }
  }

  return true;
}

export function createFormattedZonefileData(
  params: NewZonefileData
): NewZonefileData {
  if (
    !params.owner ||
    typeof params.owner !== "string" ||
    params.owner.trim() === ""
  ) {
    throw new Error("Owner field is required and must be a non-empty string");
  }

  if (params.pfp && (!isValidUrl(params.pfp) || !isValidImageUrl(params.pfp))) {
    throw new Error(
      "pfp must be a valid image URL with supported format (.png, .jpg, .jpeg, .gif, .svg, .webp)"
    );
  }

  if (params.website && !isValidUrl(params.website)) {
    throw new Error("website must be a valid URL");
  }

  if (params.social) {
    if (!Array.isArray(params.social)) {
      throw new Error("social must be an array");
    }
    for (const social of params.social) {
      if (!validateSocialEntry(social)) {
        throw new Error(
          "Invalid social entry: platform and username are required"
        );
      }
    }
  }

  if (params.addresses) {
    if (!Array.isArray(params.addresses)) {
      throw new Error("addresses must be an array");
    }
    for (const address of params.addresses) {
      if (!validateAddressEntry(address)) {
        throw new Error(
          "Invalid address entry: network, address, and type are required"
        );
      }
    }
  }

  if (params.meta) {
    if (!Array.isArray(params.meta)) {
      throw new Error("meta must be an array");
    }
    for (const meta of params.meta) {
      if (!validateMetaEntry(meta)) {
        throw new Error("Invalid meta entry: name and value are required");
      }
    }
  }

  if (params.subdomains) {
    if (!Array.isArray(params.subdomains)) {
      throw new Error("subdomains must be an array");
    }
    for (const subdomainMap of params.subdomains) {
      if (typeof subdomainMap !== "object" || subdomainMap === null) {
        throw new Error("Each subdomain entry must be an object");
      }
      for (const [subdomainName, subdomainData] of Object.entries(
        subdomainMap
      )) {
        if (!validateSubdomainEntry(subdomainData as SubdomainEntry)) {
          throw new Error(`Invalid subdomain entry for ${subdomainName}`);
        }
      }
    }
  }

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

  const formattedData: NewZonefileData = {
    owner: params.owner,
  };

  if (params.btc) formattedData.btc = params.btc;
  if (params.bio) formattedData.bio = params.bio;
  if (params.website) formattedData.website = params.website;
  if (params.pfp) formattedData.pfp = params.pfp;
  if (params.name) formattedData.name = params.name;
  if (params.location) formattedData.location = params.location;
  if (params.social && params.social.length > 0)
    formattedData.social = params.social;
  if (params.addresses && params.addresses.length > 0)
    formattedData.addresses = params.addresses;
  if (params.meta && params.meta.length > 0) formattedData.meta = params.meta;
  if (params.subdomains && params.subdomains.length > 0)
    formattedData.subdomains = params.subdomains;
  if (params.externalSubdomainsFile)
    formattedData.externalSubdomainsFile = params.externalSubdomainsFile;

  return formattedData;
}
