import { Transaction } from "@mysten/sui/transactions";
import { SUI_CLOCK_OBJECT_ID } from "@mysten/sui/utils";
import { silvanaRegistryPackage } from "./package.js";
import { fetchSuiDynamicField, fetchSuiObject } from "./fetch.js";
import { Job, JobStatus } from "./job.js";

export interface AppMethod {
  description?: string;
  developer: string;
  agent: string;
  agentMethod: string;
}

export interface AppInstance {
  id: string;
  silvanaAppName: string;
  description?: string;
  metadata: Record<string, string>;
  kv: Record<string, string>;
  methods: Record<string, AppMethod>;
  admin: string;
  sequence: number;
  blockNumber: number;
  previousBlockTimestamp: number;
  previousBlockLastSequence: number;
  lastProvedBlockNumber: number;
  lastSettledBlockNumber: number;
  lastSettledSequence: number;
  lastPurgedSequence: number;
  isPaused: boolean;
  minTimeBetweenBlocks: number;
  jobsId: string;
  createdAt: number;
  updatedAt: number;
}

export interface CreateAppJobParams {
  appInstance: string;
  description?: string;
  method: string;
  sequences?: number[];
  data: Uint8Array;
}

export class AppInstanceManager {
  private readonly registry: string;

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

  // Note: update_method and remove_method functions are not available in the Move module
  // These would need to be implemented in app_instance.move if needed

  createAppJob(params: CreateAppJobParams): Transaction {
    const { appInstance, description, method, sequences, data } = params;

    // Debug logging
    console.log("createAppJob params:", {
      appInstance,
      description,
      method,
      sequences,
      data:
        data instanceof Uint8Array ? `Uint8Array(${data.length})` : typeof data,
      dataContent: data instanceof Uint8Array ? Array.from(data) : data,
    });

    const tx = new Transaction();

    tx.moveCall({
      target: `${silvanaRegistryPackage}::app_instance::create_app_job`,
      arguments: [
        tx.object(appInstance),
        tx.pure.string(method),
        tx.pure.option("string", description ?? null),
        tx.pure.option("vector<u64>", sequences ?? null),
        tx.pure.vector("u8", data),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  startAppJob(params: { appInstance: string; jobId: number }): Transaction {
    const { appInstance, jobId } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `${silvanaRegistryPackage}::app_instance::start_app_job`,
      arguments: [
        tx.object(appInstance),
        tx.pure.u64(jobId),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  completeAppJob(params: { appInstance: string; jobId: number }): Transaction {
    const { appInstance, jobId } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `${silvanaRegistryPackage}::app_instance::complete_app_job`,
      arguments: [
        tx.object(appInstance), 
        tx.pure.u64(jobId),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  failAppJob(params: {
    appInstance: string;
    jobId: number;
    error: string;
  }): Transaction {
    const { appInstance, jobId, error } = params;
    const tx = new Transaction();

    tx.moveCall({
      target: `${silvanaRegistryPackage}::app_instance::fail_app_job`,
      arguments: [
        tx.object(appInstance),
        tx.pure.u64(jobId),
        tx.pure.string(error),
        tx.object(SUI_CLOCK_OBJECT_ID),
      ],
    });

    return tx;
  }

  async getAppInstance(
    appInstanceId: string
  ): Promise<AppInstance | undefined> {
    try {
      const appInstance = await fetchSuiObject(appInstanceId);
      if (!appInstance) return undefined;

      const fields = (appInstance as any)?.data?.content?.fields;
      if (!fields) return undefined;

      // Parse methods from VecMap
      const methods: Record<string, AppMethod> = {};
      const methodsArray = fields?.methods?.fields?.contents;
      if (Array.isArray(methodsArray)) {
        for (const entry of methodsArray) {
          const key = entry?.fields?.key;
          const value = entry?.fields?.value;
          if (key && value) {
            // Value might have a fields property too
            const methodFields = value.fields || value;
            methods[key] = {
              description: methodFields.description ?? undefined,
              developer: methodFields.developer,
              agent: methodFields.agent,
              agentMethod: methodFields.agent_method || methodFields.agentMethod,
            };
          }
        }
      }

      // Parse metadata from VecMap
      const metadata: Record<string, string> = {};
      const metadataArray = fields?.metadata?.fields?.contents;
      if (Array.isArray(metadataArray)) {
        for (const entry of metadataArray) {
          const key = entry?.fields?.key;
          const value = entry?.fields?.value;
          if (key && value) {
            metadata[key] = value;
          }
        }
      }

      // Parse kv from VecMap
      const kv: Record<string, string> = {};
      const kvArray = fields?.kv?.fields?.contents;
      if (Array.isArray(kvArray)) {
        for (const entry of kvArray) {
          const key = entry?.fields?.key;
          const value = entry?.fields?.value;
          if (key && value) {
            kv[key] = value;
          }
        }
      }

      return {
        id: fields?.id?.id,
        silvanaAppName: fields.silvana_app_name,
        description: fields?.description ?? undefined,
        metadata,
        kv,
        methods,
        admin: fields.admin,
        sequence: Number(fields.sequence),
        blockNumber: Number(fields.block_number),
        previousBlockTimestamp: Number(fields.previous_block_timestamp),
        previousBlockLastSequence: Number(fields.previous_block_last_sequence),
        lastProvedBlockNumber: Number(fields.last_proved_block_number),
        lastSettledBlockNumber: Number(fields.last_settled_block_number),
        lastSettledSequence: Number(fields.last_settled_sequence),
        lastPurgedSequence: Number(fields.last_purged_sequence),
        isPaused: Boolean(fields.isPaused),
        minTimeBetweenBlocks: Number(fields.min_time_between_blocks),
        jobsId: String(fields.jobs?.fields?.id?.id ?? ""),
        createdAt: Number(fields.created_at),
        updatedAt: Number(fields.updated_at),
      };
    } catch (error) {
      console.error("Error fetching app instance:", error);
      return undefined;
    }
  }

  async getAppJob(params: {
    appInstance: string;
    jobId: number;
  }): Promise<Job | undefined> {
    try {
      const appInstanceObj = await fetchSuiObject(params.appInstance);
      if (!appInstanceObj) return undefined;

      // Jobs are embedded in the AppInstance - use correct path
      const jobsTableId = (appInstanceObj as any)?.data?.content?.fields?.jobs?.fields?.jobs?.fields?.id?.id;
      if (!jobsTableId) return undefined;

      const job = await fetchSuiDynamicField({
        parentID: jobsTableId,
        fieldName: "jobs",
        type: "u64",
        key: String(params.jobId),
      });

      if (!job) return undefined;

      const parseStatus = (status: any): JobStatus => {
        // Check variant field format (used by Sui dynamic fields)
        if (status?.variant === "Pending") return { type: "Pending" };
        if (status?.variant === "Running") return { type: "Running" };
        if (status?.variant === "Failed") {
          // Get error from fields or from direct property
          const error = status?.fields?.error || status?.fields?.[0] || "Unknown error";
          return { type: "Failed", error };
        }
        // Legacy formats
        if (status?.Pending !== undefined) return { type: "Pending" };
        if (status?.Running !== undefined) return { type: "Running" };
        if (status?.Failed !== undefined) return { type: "Failed", error: status.Failed };
        return { type: "Pending" };
      };

      return {
        id: (job as any)?.id?.id,
        jobId: Number((job as any).job_id),
        description: (job as any)?.description ?? undefined,
        developer: (job as any).developer,
        agent: (job as any).agent,
        agentMethod: (job as any).agent_method,
        app: (job as any).app,
        appInstance: (job as any).app_instance,
        appInstanceMethod: (job as any).app_instance_method,
        sequences:
          (job as any)?.sequences?.map((s: string) => Number(s)) ?? undefined,
        data: new Uint8Array((job as any).data),
        status: parseStatus((job as any).status),
        attempts: Number((job as any).attempts),
        createdAt: Number((job as any).created_at),
        updatedAt: Number((job as any).updated_at),
      };
    } catch (error) {
      console.error("Error fetching app job:", error);
      return undefined;
    }
  }

  async getAppPendingJobs(appInstance: string): Promise<number[]> {
    try {
      const appInstanceObj = await fetchSuiObject(appInstance);
      if (!appInstanceObj) return [];

      // Jobs are embedded in the AppInstance - use correct path
      const pendingJobs = (appInstanceObj as any)?.data?.content?.fields?.jobs?.fields?.pending_jobs?.fields?.contents;
      
      if (!Array.isArray(pendingJobs)) return [];

      return pendingJobs.map((id: string) => Number(id));
    } catch (error) {
      console.error("Error fetching app pending jobs:", error);
      return [];
    }
  }
}
