import { Transaction } from "@mysten/sui/transactions";
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
import {
  fetchSuiDynamicField,
  fetchSuiDynamicFieldsList,
  fetchSuiObject,
} from "./fetch.js";

type AgentChain =
  | "ethereum-mainnet"
  | "ethereum-seplolia"
  | "ethereum-holesky"
  | "ethereum-hoodi"
  | "mina-mainnet"
  | "mina-devnet"
  | "zeko-testnet"
  | "zeko-alphanet"
  | "sui-mainnet"
  | "sui-testnet"
  | "sui-devnet"
  | "solana-mainnet"
  | "solana-testnet"
  | "solana-devnet"
  | "solana-devnet"
  | "walrus-mainnet"
  | "walrus-testnet"
  | string; // other chains

export interface Agent {
  id: string;
  name: string;
  image?: string;
  description?: string;
  site?: string;
  dockerImage: string;
  dockerSha256?: string;
  minMemoryGb: number;
  minCpuCores: number;
  supportsTEE: boolean;
  chains: AgentChain[];
  createdAt: number;
  updatedAt: number;
  version: number;
}

export interface Developer {
  id: string;
  name: string;
  github: string;
  image?: string;
  description?: string;
  site?: string;
  owner: string;
  agents: string[];
  createdAt: number;
  updatedAt: number;
  version: number;
}

export interface DeveloperNames {
  id: string;
  developer_address: string;
  names: string[];
  version: number;
}

export class AgentRegistry {
  private readonly registry: string;

  constructor(params: { registry: string }) {
    this.registry = params.registry;
  }

  static createAgentRegistry(params: { name: string }): Transaction {
    console.log("Creating agent registry", params.name);
    const transaction = new Transaction();
    transaction.moveCall({
      target: `@silvana/agent::registry::create_registry`,
      arguments: [transaction.pure.string(params.name)],
    });

    return transaction;
  }

  createDeveloper(params: {
    name: string;
    github: string;
    image?: string;
    description?: string;
    site?: string;
  }): Transaction {
    const { name, github, image, description, site } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::add_developer`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(name),
        tx.pure.string(github),
        tx.pure.option("string", image ?? null),
        tx.pure.option("string", description ?? null),
        tx.pure.option("string", site ?? null),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  updateDeveloper(params: {
    name: string;
    github: string;
    image?: string;
    description?: string;
    site?: string;
  }): Transaction {
    const { name, github, image, description, site } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::update_developer`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(name),
        tx.pure.string(github),
        tx.pure.option("string", image ?? null),
        tx.pure.option("string", description ?? null),
        tx.pure.option("string", site ?? null),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  removeDeveloper(params: { name: string; agentNames: string[] }): Transaction {
    const { name, agentNames } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::remove_developer`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(name),
        tx.pure.vector("string", agentNames),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  createAgent(params: {
    developer: string;
    name: string;
    image?: string;
    description?: string;
    site?: string;
    docker_image: string;
    docker_sha256?: string;
    min_memory_gb: number;
    min_cpu_cores: number;
    supports_tee: boolean;
    chains: AgentChain[];
  }): Transaction {
    const {
      developer,
      name,
      image,
      description,
      site,
      docker_image,
      docker_sha256,
      min_memory_gb,
      min_cpu_cores,
      supports_tee,
      chains,
    } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::add_agent`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(developer),
        tx.pure.string(name),
        tx.pure.option("string", image ?? null),
        tx.pure.option("string", description ?? null),
        tx.pure.option("string", site ?? null),
        tx.pure.string(docker_image),
        tx.pure.option("string", docker_sha256 ?? null),
        tx.pure.u16(min_memory_gb),
        tx.pure.u16(min_cpu_cores),
        tx.pure.bool(supports_tee),
        tx.pure.vector("string", chains),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  updateAgent(params: {
    developer: string;
    name: string;
    image?: string;
    description?: string;
    site?: string;
    docker_image: string;
    docker_sha256?: string;
    min_memory_gb: number;
    min_cpu_cores: number;
    supports_tee: boolean;
    chains: AgentChain[];
  }): Transaction {
    const {
      developer,
      name,
      image,
      description,
      site,
      docker_image,
      docker_sha256,
      min_memory_gb,
      min_cpu_cores,
      supports_tee,
      chains,
    } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::update_agent`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(developer),
        tx.pure.string(name),
        tx.pure.option("string", image ?? null),
        tx.pure.option("string", description ?? null),
        tx.pure.option("string", site ?? null),
        tx.pure.string(docker_image),
        tx.pure.option("string", docker_sha256 ?? null),
        tx.pure.u16(min_memory_gb),
        tx.pure.u16(min_cpu_cores),
        tx.pure.bool(supports_tee),
        tx.pure.vector("string", chains),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  removeAgent(params: { developer: string; agent: string }): Transaction {
    const { developer, agent } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `@silvana/agent::registry::remove_agent`,
      arguments: [
        tx.object(this.registry),
        tx.pure.string(developer),
        tx.pure.string(agent),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  async getDeveloper(params: { name: string }): Promise<Developer | undefined> {
    const developerObject = await fetchSuiDynamicField({
      objectID: this.registry,
      fieldName: "developers",
      type: "0x1::string::String",
      key: params.name,
    });
    if (!developerObject) {
      return undefined;
    }
    let agents: string[] = [];
    const agentsObject = (developerObject as any)?.agents?.fields?.id?.id;
    if (agentsObject) {
      const agentsList = await fetchSuiDynamicFieldsList(agentsObject);
      const agentsArray = agentsList?.data as any;
      if (Array.isArray(agentsArray)) {
        agents = agentsArray
          .map((agent: any) => agent?.name?.value)
          .filter(
            (agent: any) => agent !== undefined && typeof agent === "string"
          );
      }
    }

    const developer = {
      id: (developerObject as any)?.id?.id,
      name: (developerObject as any).name,
      github: (developerObject as any).github,
      image: (developerObject as any)?.image ?? undefined,
      description: (developerObject as any)?.description ?? undefined,
      site: (developerObject as any)?.site ?? undefined,
      owner: (developerObject as any).owner,
      agents,
      createdAt: Number((developerObject as any).created_at),
      updatedAt: Number((developerObject as any).updated_at),
      version: Number((developerObject as any).version),
    };
    if (
      !developer.id ||
      !developer.name ||
      !developer.github ||
      !developer.owner ||
      !developer.createdAt ||
      !developer.updatedAt
    ) {
      return undefined;
    }
    return developer as Developer;
  }

  async getDeveloperNames(params: {
    developerAddress: string;
  }): Promise<DeveloperNames | undefined> {
    const developerObject = await fetchSuiDynamicField({
      objectID: this.registry,
      fieldName: "developers_index",
      type: "address",
      key: params.developerAddress,
    });
    if (!developerObject) {
      return undefined;
    }
    const developer = {
      id: (developerObject as any)?.id?.id,
      developer_address: (developerObject as any).developer,
      names: (developerObject as any).names,
      version: Number((developerObject as any).version),
    };
    if (!developer.id || !developer.developer_address || !developer.names) {
      return undefined;
    }
    return developer as DeveloperNames;
  }

  async getAgent(params: {
    developer: string;
    agent: string;
  }): Promise<Agent | undefined> {
    const developerObject = await fetchSuiDynamicField({
      objectID: this.registry,
      fieldName: "developers",
      type: "0x1::string::String",
      key: params.developer,
    });

    const id = (developerObject as any)?.agents?.fields?.id?.id;
    if (!id) {
      return undefined;
    }

    const agentObject = await fetchSuiDynamicField({
      parentID: id,
      fieldName: "agents",
      type: "0x1::string::String",
      key: params.agent,
    });
    if (!agentObject) {
      return undefined;
    }
    const agent = {
      id: (agentObject as any)?.id?.id,
      name: (agentObject as any).name,
      image: (agentObject as any)?.image ?? undefined,
      description: (agentObject as any)?.description ?? undefined,
      site: (agentObject as any)?.site ?? undefined,
      dockerImage: (agentObject as any).docker_image,
      dockerSha256: (agentObject as any)?.docker_sha256 ?? undefined,
      minMemoryGb: Number((agentObject as any).min_memory_gb),
      minCpuCores: Number((agentObject as any).min_cpu_cores),
      supportsTEE: Boolean((agentObject as any).supports_tee),
      createdAt: Number((agentObject as any).created_at),
      updatedAt: Number((agentObject as any).updated_at),
      version: Number((agentObject as any).version),
    };
    if (
      !agent.id ||
      !agent.name ||
      !agent.dockerImage ||
      !agent.minMemoryGb ||
      !agent.minCpuCores ||
      !agent.createdAt ||
      !agent.updatedAt
    ) {
      return undefined;
    }
    return agent as Agent;
  }

  static async getDockerImageDetails(params: { dockerImage: string }): Promise<
    | {
        sha256: string;
        numberOfLayers: number;
      }
    | undefined
  > {
    try {
      const { dockerImage } = params;

      // Parse image_source to extract repository and tag
      const colonPos = dockerImage.lastIndexOf(":");
      const repository =
        colonPos !== -1 ? dockerImage.slice(0, colonPos) : dockerImage;
      const tag = colonPos !== -1 ? dockerImage.slice(colonPos + 1) : "latest";

      // 1. Get token
      const tokenResponse = await fetch(
        "https://auth.docker.io/token?" +
          new URLSearchParams({
            service: "registry.docker.io",
            scope: `repository:${repository}:pull`,
          })
      );

      if (!tokenResponse.ok) {
        return undefined;
      }

      const tokenData = await tokenResponse.json();
      const token = tokenData.token;

      if (!token) {
        return undefined;
      }

      // 2. Fetch manifest/index
      const manifestResponse = await fetch(
        `https://registry-1.docker.io/v2/${repository}/manifests/${tag}`,
        {
          headers: {
            Authorization: `Bearer ${token}`,
            Accept:
              "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json, application/vnd.docker.distribution.manifest.v2+json",
          },
        }
      );

      if (!manifestResponse.ok) {
        return undefined;
      }

      const contentType = manifestResponse.headers.get("content-type") || "";

      // Extract the digest from the response headers
      let digest = manifestResponse.headers.get("docker-content-digest") || "";

      let manifest: any;

      if (contentType.includes("index") || contentType.includes("list")) {
        // This is a manifest index (multi-platform)
        const idx = await manifestResponse.json();

        // Pick amd64/linux manifest
        const platformManifest = idx.manifests?.find(
          (m: any) =>
            m.platform?.architecture === "amd64" && m.platform?.os === "linux"
        );

        if (!platformManifest) {
          return undefined;
        }

        const platformDigest = platformManifest.digest;

        // 3. Fetch the actual manifest
        const actualManifestResponse = await fetch(
          `https://registry-1.docker.io/v2/${repository}/manifests/${platformDigest}`,
          {
            headers: {
              Authorization: `Bearer ${token}`,
              Accept: "application/vnd.docker.distribution.manifest.v2+json",
            },
          }
        );

        if (!actualManifestResponse.ok) {
          return undefined;
        }

        manifest = await actualManifestResponse.json();

        // Update digest from the actual manifest response
        const actualDigest = actualManifestResponse.headers.get(
          "docker-content-digest"
        );
        if (actualDigest) {
          digest = actualDigest;
        }
      } else {
        // This is already a direct manifest (single platform)
        manifest = await manifestResponse.json();
      }

      if (!manifest?.layers || !Array.isArray(manifest.layers)) {
        return undefined;
      }

      const numberOfLayers = manifest.layers.length;

      // Remove the "sha256:" prefix if present
      const sha256 = digest.startsWith("sha256:") ? digest.slice(7) : digest;

      if (!sha256) {
        return undefined;
      }

      return {
        sha256,
        numberOfLayers,
      };
    } catch (error) {
      console.error("Error fetching Docker image details:", error);
      return undefined;
    }
  }
}
