import chalk from "chalk";
import { promises as fs } from "fs";
import path from "path";
import { createHash } from "crypto";
import { ethers } from "ethers";
import { CapacityCreditInfo } from "../utils/mint-cc";
import { PKPInfo } from "../utils/mint-pkp";
import { PermissionData } from "@lit-protocol/vincent-contracts-sdk";

interface AccountState {
  privateKey: string;
  address: string;
  createdAt: string;
  network: string;
}

interface PKPState extends PKPInfo {
  createdAt: string;
  network: string;
}

interface CapacityCreditState extends CapacityCreditInfo {
  network: string;
  expiresAt: string; // ISO timestamp of when the capacity credits expire
}

interface VincentAppState {
  appId: number;
  appVersion: number;
  createdAt: string;
  network: string;
  delegateeAddress: string;
  abilityIpfsCids: string[];
  abilityPolicies: string[][];
  policyParams: PermissionData;
}

interface PKPAppPermissionState {
  pkpEthAddress: string;
  appId: number;
  appVersion: number;
  permittedAt: string;
  network: string;
}

interface ConfigurationState {
  configHash: string;
  abilityIpfsCids: string[];
  abilityPolicyCids: string[][];
  lastUsed: string;
  network: string;
  state: {
    vincentApp?: VincentAppState;
    pkpAppPermissions?: PKPAppPermissionState[];
  };
}

interface AppVersionConfiguration {
  appId: number;
  appVersion: number;
  abilityIpfsCids: string[];
  abilityPolicyCids: string[][];
  lastUsed: string;
  network: string;
  state: {
    vincentApp: VincentAppState;
    pkpAppPermissions?: PKPAppPermissionState[];
  };
}

interface NestedE2EState {
  version: string;
  // Keep legacy structures for backward compatibility
  sharedAccounts?: {
    [network: string]: {
      appManager?: AccountState;
      appDelegatee?: AccountState;
      agentWalletPkpOwner?: AccountState;
    };
  };
  sharedPkps?: {
    [network: string]: PKPState;
  };
  sharedCapacityCredits?: {
    [network: string]: CapacityCreditState;
  };
  testFiles: {
    [fileName: string]: {
      // Per-test-file accounts (isolated)
      accounts?: {
        [network: string]: {
          appManager?: AccountState;
          appDelegatee?: AccountState;
          agentWalletPkpOwner?: AccountState;
        };
      };
      // Per-test-file PKPs (isolated)
      pkps?: {
        [network: string]: PKPState;
      };
      // Per-test-file capacity credits (isolated)
      capacityCredits?: {
        [network: string]: CapacityCreditState;
      };
      configurations: {
        [configHash: string]: ConfigurationState;
      };
      // New app-version-based configurations
      appVersions: {
        [appVersionKey: string]: AppVersionConfiguration; // key format: "appId-appVersion"
      };
    };
  };
}

const STATE_FILE_PATH = path.join(process.cwd(), ".e2e-state.json");
const STATE_VERSION = "2.0.0";

export class StateManager {
  nestedState: NestedE2EState = {
    version: STATE_VERSION,
    testFiles: {},
  };

  private currentConfigState: ConfigurationState | null = null;

  constructor(
    private network: string,
    private testFileName: string,
    private configHash: string,
    abilityIpfsCids?: string[],
    abilityPolicyCids?: string[][]
  ) {
    // If ability/policy CIDs are provided, generate the real config hash
    if (abilityIpfsCids && abilityPolicyCids) {
      this.configHash = StateManager.generateConfigHash(
        abilityIpfsCids,
        abilityPolicyCids
      );
    }

    // Initialize the current configuration state
    this.currentConfigState = this.getCurrentConfigState();
    console.log(
      `🏗️  StateManager initialized with nested structure v${STATE_VERSION} for ${testFileName}:${this.configHash}`
    );
  }

  /**
   * Generate configuration hash from ability and policy IPFS CIDs
   */
  static generateConfigHash(
    abilityIpfsCids: string[],
    abilityPolicyCids: string[][]
  ): string {
    // Combine all IPFS CIDs and create a hash
    const flattenedPolicyCids = abilityPolicyCids.flat();
    const allCids = [...abilityIpfsCids, ...flattenedPolicyCids];
    const sortedCids = allCids.sort();
    const cidString = sortedCids.join("");
    const hash = createHash("sha256")
      .update(cidString)
      .digest("hex")
      .substring(0, 8);

    return hash;
  }

  /**
   * Auto-detect test filename from call stack
   */
  static autoDetectTestFileName(): string {
    const stack = new Error().stack;
    if (!stack) return "unknown-test.ts";

    const stackLines = stack.split("\n");
    for (const line of stackLines) {
      const match = line.match(/\/([^\/\s]+\.ts):/);
      if (match && match[1] !== "state-manager.ts" && match[1] !== "init.ts") {
        return match[1];
      }
    }
    return "unknown-test.ts";
  }

  /**
   * Get current configuration state, creating if needed
   */
  private getCurrentConfigState(): ConfigurationState {
    if (!this.nestedState.testFiles[this.testFileName]) {
      this.nestedState.testFiles[this.testFileName] = {
        accounts: {},
        pkps: {},
        capacityCredits: {},
        configurations: {},
        appVersions: {},
      };
    }

    // Ensure all structures exist for this test file
    if (!this.nestedState.testFiles[this.testFileName].accounts) {
      this.nestedState.testFiles[this.testFileName].accounts = {};
    }
    if (!this.nestedState.testFiles[this.testFileName].pkps) {
      this.nestedState.testFiles[this.testFileName].pkps = {};
    }
    if (!this.nestedState.testFiles[this.testFileName].capacityCredits) {
      this.nestedState.testFiles[this.testFileName].capacityCredits = {};
    }

    if (
      !this.nestedState.testFiles[this.testFileName].configurations[
      this.configHash
      ]
    ) {
      this.nestedState.testFiles[this.testFileName].configurations[
        this.configHash
      ] = {
        configHash: this.configHash,
        abilityIpfsCids: [],
        abilityPolicyCids: [],
        lastUsed: new Date().toISOString(),
        network: this.network,
        state: {},
      };
    }

    return this.nestedState.testFiles[this.testFileName].configurations[
      this.configHash
    ];
  }

  /**
   * Set the real configuration hash based on ability/policy CIDs
   * This should be called before any state operations when CIDs are known
   */
  setConfigurationFromCIDs(
    abilityIpfsCids: string[],
    abilityPolicyCids: string[][]
  ): void {
    const newConfigHash = StateManager.generateConfigHash(
      abilityIpfsCids,
      abilityPolicyCids
    );

    if (newConfigHash !== this.configHash) {
      console.log(
        `🔄 Setting configuration from ${this.configHash} to ${newConfigHash}`
      );

      // Update the internal config hash
      (this as any).configHash = newConfigHash;

      // Reset current config state since we're switching to a new config
      this.currentConfigState = null;

      // Initialize the new configuration state
      this.currentConfigState = this.getCurrentConfigState();

      // Update metadata
      this.updateConfigurationMetadata(abilityIpfsCids, abilityPolicyCids);
    } else {
      // Update metadata even if hash is same (for lastUsed timestamp)
      this.updateConfigurationMetadata(abilityIpfsCids, abilityPolicyCids);
    }
  }

  /**
   * Update configuration metadata
   */
  updateConfigurationMetadata(
    abilityIpfsCids: string[],
    abilityPolicyCids: string[][]
  ) {
    const configState = this.getCurrentConfigState();
    configState.abilityIpfsCids = abilityIpfsCids;
    configState.abilityPolicyCids = abilityPolicyCids;
    configState.lastUsed = new Date().toISOString();
  }

  /**
   * Generate app version key from appId and appVersion
   */
  private static generateAppVersionKey(
    appId: number,
    appVersion: number
  ): string {
    return `${appId}-${appVersion}`;
  }

  // /**
  //  * Get app version configuration, creating if needed
  //  */
  // private getAppVersionConfiguration(
  //   appId: string,
  //   appVersion: string
  // ): AppVersionConfiguration {
  //   const appVersionKey = StateManager.generateAppVersionKey(appId, appVersion);

  //   if (!this.nestedState.testFiles[this.testFileName]) {
  //     this.nestedState.testFiles[this.testFileName] = {
  //       configurations: {},
  //       appVersions: {},
  //     };
  //   }

  //   if (
  //     !this.nestedState.testFiles[this.testFileName].appVersions[appVersionKey]
  //   ) {
  //     throw new Error(
  //       `App version configuration ${appVersionKey} not found. It should be created when saving the app.`
  //     );
  //   }

  //   return this.nestedState.testFiles[this.testFileName].appVersions[
  //     appVersionKey
  //   ];
  // }

  /**
   * Save app version configuration
   */
  private saveAppVersionConfiguration(
    appId: number,
    appVersion: number,
    vincentApp: VincentAppState,
    abilityIpfsCids: string[],
    abilityPolicyCids: string[][]
  ): void {
    const appVersionKey = StateManager.generateAppVersionKey(appId, appVersion);

    if (!this.nestedState.testFiles[this.testFileName]) {
      this.nestedState.testFiles[this.testFileName] = {
        configurations: {},
        appVersions: {},
      };
    }

    this.nestedState.testFiles[this.testFileName].appVersions[appVersionKey] = {
      appId,
      appVersion,
      abilityIpfsCids,
      abilityPolicyCids,
      lastUsed: new Date().toISOString(),
      network: this.network,
      state: {
        vincentApp,
        pkpAppPermissions: [],
      },
    };

    console.log(
      chalk.green(`💾 Saved app version configuration: ${appVersionKey}`)
    );
  }

  /**
     * Get or register Vincent app using app versioning approach
     */
  async getOrRegisterVincentAppVersioned(
    delegateeAddress: string,
    registerAppFunction: () => Promise<{ appId: number; appVersion: number }>,
    registerNextVersionFunction: (
      appId: number
    ) => Promise<{ appId: number; appVersion: number }>,
    abilityIpfsCids: string[],
    abilityPolicies: string[][],
    policyParams: PermissionData,
  ): Promise<{
    appId: number;
    appVersion: number;
    isNew: boolean;
    isNewVersion: boolean;
    abilityIpfsCids: string[];
    abilityPolicies: string[][];
    policyParams: PermissionData;
  }> {
    // First, check if we have any existing app for this delegatee
    const existingApp = this.getExistingVincentApp(delegateeAddress);

    if (existingApp) {
      const appId = existingApp.appId;

      // Check if current abilities/policies match any existing app version
      const matchingAppVersion = this.findMatchingAppVersion(
        appId,
        abilityIpfsCids,
        abilityPolicies
      );

      if (matchingAppVersion) {
        console.log(
          chalk.blue(
            `🏢 Using existing app version: ${appId} v${matchingAppVersion}`
          )
        );
        return {
          appId,
          appVersion: matchingAppVersion,
          isNew: false,
          isNewVersion: false,
          abilityIpfsCids,
          abilityPolicies,
          policyParams,
        };
      } else {
        // Register new version for existing app
        console.log(
          chalk.yellow(`🏢 Registering new version for existing app: ${appId}`)
        );
        const { appVersion } = await registerNextVersionFunction(appId);

        // Save the new app version
        const vincentApp: VincentAppState = {
          appId,
          appVersion,
          delegateeAddress,
          abilityIpfsCids,
          abilityPolicies,
          policyParams,
          createdAt: new Date().toISOString(),
          network: this.network,
        };

        this.saveAppVersionConfiguration(
          appId,
          appVersion,
          vincentApp,
          abilityIpfsCids,
          abilityPolicies
        );

        return {
          appId,
          appVersion,
          isNew: false,
          isNewVersion: true,
          abilityIpfsCids,
          abilityPolicies,
          policyParams,
        };
      }
    } else {
      // Register completely new app
      console.log(chalk.yellow(`🏢 Registering new Vincent app...`));
      const { appId, appVersion } = await registerAppFunction();

      // Save the new app version
      const vincentApp: VincentAppState = {
        appId,
        appVersion,
        delegateeAddress,
        abilityIpfsCids,
        abilityPolicies,
        policyParams,
        createdAt: new Date().toISOString(),
        network: this.network,
      };

      this.saveAppVersionConfiguration(
        appId,
        appVersion,
        vincentApp,
        abilityIpfsCids,
        abilityPolicies
      );

      // Also save to current config for backward compatibility
      this.saveVincentApp(
        appId,
        appVersion,
        delegateeAddress,
        abilityIpfsCids,
        abilityPolicies,
        policyParams,
      );

      return {
        appId,
        appVersion,
        isNew: true,
        isNewVersion: false,
        abilityIpfsCids,
        abilityPolicies,
        policyParams,
      };
    }
  }

  /**
   * Find matching app version based on ability/policy CIDs
   */
  private findMatchingAppVersion(
    appId: number,
    abilityIpfsCids: string[],
    abilityPolicies: string[][]
  ): number | undefined {
    if (!this.nestedState.testFiles[this.testFileName]?.appVersions) {
      return undefined;
    }

    const appVersions =
      this.nestedState.testFiles[this.testFileName].appVersions;

    for (const [appVersionKey, config] of Object.entries(appVersions)) {
      if (config.appId === appId && config.network === this.network) {
        // Check if ability CIDs match
        const abilityCidsMatch =
          JSON.stringify(config.abilityIpfsCids.sort()) ===
          JSON.stringify(abilityIpfsCids.sort());
        const policyCidsMatch =
          JSON.stringify(config.abilityPolicyCids) ===
          JSON.stringify(abilityPolicies);

        if (abilityCidsMatch && policyCidsMatch) {
          console.log(
            chalk.blue(`🔍 Found matching app version: ${appVersionKey}`)
          );
          return config.appVersion;
        }
      }
    }

    console.log(
      chalk.yellow(
        `🔍 No matching app version found for appId ${appId} with current abilities/policies`
      )
    );
    return undefined;
  }

  async loadState(): Promise<void> {
    try {
      const data = await fs.readFile(STATE_FILE_PATH, "utf-8");
      const loadedState = JSON.parse(data);

      // Validate version compatibility
      if (loadedState.version !== STATE_VERSION) {
        console.warn(
          chalk.yellow(
            `⚠️  State file version mismatch. Expected ${STATE_VERSION}, got ${loadedState.version}. Creating new state.`
          )
        );
        return;
      }

      this.nestedState = loadedState;
      this.currentConfigState = this.getCurrentConfigState();
      console.log(
        chalk.blue(
          `📁 Loaded existing state for ${this.testFileName}:${this.configHash}`
        )
      );
    } catch (error) {
      // File doesn't exist or is invalid - that's ok, we'll create it
      console.log(
        chalk.blue("📝 No existing state file found. Will create one.")
      );
      this.currentConfigState = this.getCurrentConfigState();
    }
  }

  async saveState(): Promise<void> {
    try {
      // Update last used timestamp
      if (this.currentConfigState) {
        this.currentConfigState.lastUsed = new Date().toISOString();
      }

      await fs.writeFile(
        STATE_FILE_PATH,
        JSON.stringify(this.nestedState, null, 2),
        "utf-8"
      );
      console.log(
        chalk.gray(`💾 Saved state for ${this.testFileName}:${this.configHash}`)
      );
    } catch (error) {
      console.error(chalk.red("❌ Failed to save state:"), error);
      throw error;
    }
  }

  generateAccount(): AccountState {
    const wallet = ethers.Wallet.createRandom();
    return {
      privateKey: wallet.privateKey,
      address: wallet.address,
      createdAt: new Date().toISOString(),
      network: this.network,
    };
  }

  getOrGenerateAccount(
    accountType: "appManager" | "appDelegatee" | "agentWalletPkpOwner",
    existingPrivateKey?: string
  ): { privateKey: string; address: string; isNew: boolean } {
    // If private key provided from env, use it
    if (existingPrivateKey) {
      const wallet = new ethers.Wallet(existingPrivateKey);
      return {
        privateKey: existingPrivateKey,
        address: wallet.address,
        isNew: false,
      };
    }

    // Ensure current config state is initialized (which initializes accounts structure)
    this.getCurrentConfigState();

    // Get accounts for this test file and network
    const testFileAccounts =
      this.nestedState.testFiles[this.testFileName].accounts!;

    // Ensure network accounts exist for this test file
    if (!testFileAccounts[this.network]) {
      testFileAccounts[this.network] = {};
    }

    // Check if we have a saved account for this type in this test file
    const savedAccount = testFileAccounts[this.network][accountType];

    if (savedAccount && savedAccount.network === this.network) {
      console.log(
        chalk.blue(
          `🔑 Using ${accountType} from ${this.testFileName}: ${savedAccount.address}`
        )
      );
      return {
        privateKey: savedAccount.privateKey,
        address: savedAccount.address,
        isNew: false,
      };
    }

    // Check for legacy shared account and migrate if exists
    const legacyAccount =
      this.nestedState.sharedAccounts?.[this.network]?.[accountType];
    if (legacyAccount && legacyAccount.network === this.network) {
      console.log(
        chalk.blue(
          `🔄 Migrating shared ${accountType} to ${this.testFileName}: ${legacyAccount.address}`
        )
      );
      testFileAccounts[this.network][accountType] = legacyAccount;
      return {
        privateKey: legacyAccount.privateKey,
        address: legacyAccount.address,
        isNew: false,
      };
    }

    // Generate new account for this test file
    console.log(
      chalk.yellow(
        `🔑 Generating new ${accountType} account for ${this.testFileName}`
      )
    );
    const newAccount = this.generateAccount();
    testFileAccounts[this.network][accountType] = newAccount;

    return {
      privateKey: newAccount.privateKey,
      address: newAccount.address,
      isNew: true,
    };
  }

  // getGeneratedAccounts(): string[] {
  //   // Get accounts for this test file
  //   const testFileAccounts =
  //     this.nestedState.testFiles[this.testFileName]?.accounts?.[this.network] ||
  //     {};

  //   // Also check legacy shared accounts for backward compatibility
  //   const legacyAccounts =
  //     this.nestedState.sharedAccounts?.[this.network] || {};

  //   // Combine both sources
  //   const allAccounts = { ...legacyAccounts, ...testFileAccounts };

  //   return Object.entries(allAccounts)
  //     .filter(([_, account]) => account !== undefined)
  //     .map(([type, account]) => `${type}: ${account!.address}`);
  // }

  /**
   * Get existing PKP or return undefined if needs minting
   */
  getExistingPKP(): PKPState | undefined {
    // Ensure current config state is initialized
    this.getCurrentConfigState();

    // Get PKP for this test file and network
    const testFilePkps = this.nestedState.testFiles[this.testFileName].pkps!;
    const existingPkp = testFilePkps[this.network];

    if (existingPkp && existingPkp.network === this.network) {
      console.log(
        chalk.blue(
          `🔑 Using PKP from ${this.testFileName}: ${existingPkp.ethAddress}`
        )
      );
      return existingPkp;
    }

    // Check for legacy shared PKP and migrate if exists
    const legacyPkp = this.nestedState.sharedPkps?.[this.network];
    if (legacyPkp && legacyPkp.network === this.network) {
      console.log(
        chalk.blue(
          `🔄 Migrating shared PKP to ${this.testFileName}: ${legacyPkp.ethAddress}`
        )
      );
      testFilePkps[this.network] = legacyPkp;
      return legacyPkp;
    }

    return undefined;
  }

  /**
   * Save a newly minted PKP to state
   */
  savePKP(pkpInfo: PKPInfo): void {
    // Ensure current config state is initialized
    this.getCurrentConfigState();

    // Save PKP for this test file and network
    const testFilePkps = this.nestedState.testFiles[this.testFileName].pkps!;
    testFilePkps[this.network] = {
      ...pkpInfo,
      createdAt: new Date().toISOString(),
      network: this.network,
    };

    console.log(
      chalk.gray(`💾 Saved PKP for ${this.testFileName}: ${pkpInfo.ethAddress}`)
    );
  }

  /**
   * Get or mint the single testing PKP
   */
  async getOrMintPKP(
    mintFunction: () => Promise<PKPInfo>
  ): Promise<{ pkp: PKPInfo; isNew: boolean }> {
    // Check if we have an existing PKP
    const existingPKP = this.getExistingPKP();
    if (existingPKP) {
      return {
        pkp: {
          tokenId: existingPKP.tokenId,
          publicKey: existingPKP.publicKey,
          ethAddress: existingPKP.ethAddress,
        },
        isNew: false,
      };
    }

    // Mint new PKP
    console.log(chalk.yellow(`🔨 Minting new testing PKP...`));
    const newPKP = await mintFunction();

    // Save to state
    this.savePKP(newPKP);

    return {
      pkp: newPKP,
      isNew: true,
    };
  }

  /**
   * Check if capacity credits are still valid (not expired within 1 day)
   */
  private isCapacityCreditValid(capacityCredit: CapacityCreditState): boolean {
    const expirationDate = new Date(capacityCredit.expiresAt);
    const now = new Date();
    const oneDayFromNow = new Date(now.getTime() + 24 * 60 * 60 * 1000);

    // Consider expired if it expires within the next 24 hours
    const isValid = expirationDate > oneDayFromNow;

    // Add helpful logging when expired/expiring
    if (!isValid) {
      console.log(
        chalk.yellow(
          `   ↳ Capacity credits expire at: ${expirationDate.toISOString()}`
        )
      );
      console.log(chalk.yellow(`   ↳ Current time: ${now.toISOString()}`));
    }

    return isValid;
  }

  /**
   * Get existing capacity credits or return undefined if needs minting
   */
  getExistingCapacityCredits(): CapacityCreditState | undefined {
    // Ensure current config state is initialized
    this.getCurrentConfigState();

    // Get capacity credits for this test file and network
    const testFileCC =
      this.nestedState.testFiles[this.testFileName].capacityCredits!;
    const existingCC = testFileCC[this.network];

    if (existingCC && existingCC.network === this.network) {
      if (this.isCapacityCreditValid(existingCC)) {
        console.log(
          chalk.blue(
            `🎫 Using capacity credits from ${this.testFileName}: ${existingCC.capacityTokenIdStr}`
          )
        );
        console.log(chalk.blue(`   ↳ Expires: ${existingCC.expiresAt}`));
        return existingCC;
      } else {
        console.log(
          chalk.yellow(
            `⚠️  Capacity credits found but expired/expiring soon for ${this.testFileName}`
          )
        );
        console.log(
          chalk.yellow(`   ↳ Token ID: ${existingCC.capacityTokenIdStr}`)
        );
      }
    }

    // Check for legacy shared capacity credits and migrate if exists
    const legacyCC = this.nestedState.sharedCapacityCredits?.[this.network];
    if (legacyCC && legacyCC.network === this.network) {
      if (this.isCapacityCreditValid(legacyCC)) {
        console.log(
          chalk.blue(
            `🔄 Migrating shared capacity credits to ${this.testFileName}: ${legacyCC.capacityTokenIdStr}`
          )
        );
        testFileCC[this.network] = legacyCC;
        return legacyCC;
      }
    }

    return undefined;
  }

  /**
   * Save newly minted capacity credits to state
   */
  saveCapacityCredits(capacityCreditInfo: CapacityCreditInfo): void {
    // Calculate expiration date using the same logic as mint-cc.ts
    const mintedDate = new Date(capacityCreditInfo.mintedAtUtc);
    const expirationDate = new Date(
      Date.UTC(
        mintedDate.getUTCFullYear(),
        mintedDate.getUTCMonth(),
        mintedDate.getUTCDate() +
        capacityCreditInfo.daysUntilUTCMidnightExpiration,
        0,
        0,
        0,
        0
      )
    );

    // Ensure current config state is initialized
    this.getCurrentConfigState();

    // Save capacity credits for this test file and network
    const testFileCC =
      this.nestedState.testFiles[this.testFileName].capacityCredits!;
    testFileCC[this.network] = {
      ...capacityCreditInfo,
      network: this.network,
      expiresAt: expirationDate.toISOString(),
    };

    console.log(
      chalk.green(
        `💾 Saved capacity credits for ${this.testFileName}: ${capacityCreditInfo.capacityTokenIdStr}`
      )
    );
    console.log(chalk.green(`   ↳ Expires: ${expirationDate.toISOString()}`));
  }

  /**
   * Get or mint capacity credits for testing
   */
  async getOrMintCapacityCredits(
    mintFunction: () => Promise<CapacityCreditInfo>
  ): Promise<{ capacityCredits: CapacityCreditInfo; isNew: boolean }> {
    // Check if we have existing valid capacity credits
    const existingCC = this.getExistingCapacityCredits();
    if (existingCC) {
      return {
        capacityCredits: {
          capacityTokenIdStr: existingCC.capacityTokenIdStr,
          capacityTokenId: existingCC.capacityTokenId,
          requestsPerKilosecond: existingCC.requestsPerKilosecond,
          daysUntilUTCMidnightExpiration:
            existingCC.daysUntilUTCMidnightExpiration,
          mintedAtUtc: existingCC.mintedAtUtc,
        },
        isNew: false,
      };
    }

    // Mint new capacity credits
    console.log(chalk.yellow(`🎫 Minting new capacity credits...`));
    const newCC = await mintFunction();

    // Save to state
    this.saveCapacityCredits(newCC);

    return {
      capacityCredits: newCC,
      isNew: true,
    };
  }

  /**
   * Get existing Vincent app or return undefined if needs registration
   * First checks current config, then searches all configs for apps with same delegatee
   */
  getExistingVincentApp(delegateeAddress: string): VincentAppState | undefined {
    const configState = this.getCurrentConfigState();
    const existingApp = configState.state.vincentApp;

    if (
      existingApp &&
      existingApp.network === this.network &&
      existingApp.delegateeAddress === delegateeAddress
    ) {
      console.log(
        chalk.blue(
          `🏢 Using saved Vincent app for config ${this.configHash}: ${existingApp.appId}`
        )
      );
      console.log(chalk.blue(`   ↳ Version: ${existingApp.appVersion}`));
      console.log(
        chalk.blue(`   ↳ Delegatee: ${existingApp.delegateeAddress}`)
      );
      return existingApp;
    }

    // If no app in current config, search all configs for apps with same delegatee
    // This handles the case where the smart contract prevents the same delegatee from registering multiple apps
    if (this.nestedState.testFiles[this.testFileName]) {
      const allConfigs =
        this.nestedState.testFiles[this.testFileName].configurations;
      for (const [configHash, config] of Object.entries(allConfigs)) {
        if (configHash === this.configHash) continue; // Skip current config (already checked)

        const appInOtherConfig = config.state.vincentApp;
        if (
          appInOtherConfig &&
          appInOtherConfig.network === this.network &&
          appInOtherConfig.delegateeAddress === delegateeAddress
        ) {
          console.log(
            chalk.yellow(
              `🏢 Found existing Vincent app in different config ${configHash}: ${appInOtherConfig.appId}`
            )
          );
          console.log(
            chalk.yellow(`   ↳ Version: ${appInOtherConfig.appVersion}`)
          );
          console.log(
            chalk.yellow(`   ↳ Delegatee: ${appInOtherConfig.delegateeAddress}`)
          );
          console.log(
            chalk.yellow(
              `   ↳ Copying app to current config ${this.configHash}`
            )
          );

          // Copy the app to current configuration since same delegatee can't register twice
          configState.state.vincentApp = {
            ...appInOtherConfig,
            // Update metadata to reflect current configuration
            createdAt: new Date().toISOString(),
          };

          return appInOtherConfig;
        }
      }
    }

    return undefined;
  }

  /**
   * Save a newly registered Vincent app to state
   */
  saveVincentApp(
    appId: number,
    appVersion: number,
    delegateeAddress: string,
    abilityIpfsCids: string[],
    abilityPolicies: string[][],
    policyParams: PermissionData,
  ): void {
    const configState = this.getCurrentConfigState();
    configState.state.vincentApp = {
      appId,
      appVersion,
      delegateeAddress,
      abilityIpfsCids,
      abilityPolicies,
      policyParams,
      createdAt: new Date().toISOString(),
      network: this.network,
    };

    console.log(
      chalk.green(
        `💾 Saved Vincent app for config ${this.configHash}: ${appId}`
      )
    );
    console.log(chalk.green(`   ↳ Version: ${appVersion}`));
    console.log(chalk.green(`   ↳ Delegatee: ${delegateeAddress}`));
  }

  // /**
  //  * Get or register Vincent app for testing
  //  */
  // async getOrRegisterVincentApp(
  //   delegateeAddress: string,
  //   registerFunction: () => Promise<{ appId: string; appVersion: string }>,
  //   abilityIpfsCids: string[],
  //   abilityPolicies: string[][],
  //   abilityPolicyParameterNames: string[][],
  //   abilityPolicyParameterTypes: number[][],
  //   abilityPolicyParameterValues?: string[][]
  // ): Promise<{
  //   appId: string;
  //   appVersion: string;
  //   isNew: boolean;
  //   abilityIpfsCids: string[];
  //   abilityPolicies: string[][];
  //   abilityPolicyParameterNames: string[][];
  //   abilityPolicyParameterTypes: number[][];
  //   abilityPolicyParameterValues?: string[][];
  // }> {
  //   // Check if we have an existing Vincent app for this delegatee
  //   const existingApp = this.getExistingVincentApp(delegateeAddress);
  //   if (existingApp) {
  //     // Update the current configuration with the new ability/policy information
  //     // since we're reusing an existing app but with potentially different abilities
  //     const configState = this.getCurrentConfigState();
  //     if (configState.state.vincentApp) {
  //       console.log(
  //         chalk.blue(
  //           `🔄 Updating reused app ${existingApp.appId} with current ability/policy configuration`
  //         )
  //       );
  //       configState.state.vincentApp.abilityIpfsCids = abilityIpfsCids;
  //       configState.state.vincentApp.abilityPolicies = abilityPolicies;
  //       configState.state.vincentApp.abilityPolicyParameterNames =
  //         abilityPolicyParameterNames;
  //       configState.state.vincentApp.abilityPolicyParameterTypes =
  //         abilityPolicyParameterTypes;
  //       configState.state.vincentApp.abilityPolicyParameterValues =
  //         abilityPolicyParameterValues;
  //     }

  //     return {
  //       appId: existingApp.appId,
  //       appVersion: existingApp.appVersion,
  //       isNew: false,
  //       abilityIpfsCids,
  //       abilityPolicies,
  //       abilityPolicyParameterNames,
  //       abilityPolicyParameterTypes,
  //       abilityPolicyParameterValues,
  //     };
  //   }

  //   // Register new Vincent app
  //   console.log(chalk.yellow(`🏢 Registering new Vincent app...`));
  //   const { appId, appVersion } = await registerFunction();

  //   // Save to state
  //   this.saveVincentApp(
  //     appId,
  //     appVersion,
  //     delegateeAddress,
  //     abilityIpfsCids,
  //     abilityPolicies,
  //     abilityPolicyParameterNames,
  //     abilityPolicyParameterTypes,
  //     abilityPolicyParameterValues
  //   );

  //   return {
  //     appId,
  //     appVersion,
  //     isNew: true,
  //     abilityIpfsCids,
  //     abilityPolicies,
  //     abilityPolicyParameterNames,
  //     abilityPolicyParameterTypes,
  //     abilityPolicyParameterValues,
  //   };
  // }

  /**
   * Update existing Vincent app state with parameter values
   */
  updateVincentAppParameterValues(
    appId: number,
    policyParams: PermissionData,
  ): void {
    const configState = this.getCurrentConfigState();
    if (
      configState.state.vincentApp &&
      configState.state.vincentApp.appId === appId
    ) {
      configState.state.vincentApp.policyParams = policyParams;
      console.log(
        chalk.green(
          `💾 Updated Vincent app parameter values for config ${this.configHash}: ${appId}`
        )
      );
    }
  }

  /**
   * Check if PKP is already permitted for the given app version
   */
  isPKPPermittedForAppVersion(
    pkpEthAddress: string,
    appId: number,
    appVersion: number
  ): boolean {
    const configState = this.getCurrentConfigState();
    if (!configState.state.pkpAppPermissions) {
      return false;
    }

    const existingPermission = configState.state.pkpAppPermissions.find(
      (permission) =>
        permission.pkpEthAddress === pkpEthAddress &&
        permission.appId === appId &&
        permission.appVersion === appVersion &&
        permission.network === this.network
    );

    if (existingPermission) {
      console.log(
        chalk.blue(
          `🔐 PKP ${pkpEthAddress} already permitted for app ${appId} v${appVersion} in config ${this.configHash}`
        )
      );
      console.log(
        chalk.blue(`   ↳ Permitted at: ${existingPermission.permittedAt}`)
      );
      return true;
    }

    return false;
  }

  /**
   * Save PKP app permission to state
   */
  savePKPAppPermission(
    pkpEthAddress: string,
    appId: number,
    appVersion: number
  ): void {
    const configState = this.getCurrentConfigState();
    if (!configState.state.pkpAppPermissions) {
      configState.state.pkpAppPermissions = [];
    }

    // Check if permission already exists to avoid duplicates
    const existingIndex = configState.state.pkpAppPermissions.findIndex(
      (permission) =>
        permission.pkpEthAddress === pkpEthAddress &&
        permission.appId === appId &&
        permission.appVersion === appVersion &&
        permission.network === this.network
    );

    const newPermission: PKPAppPermissionState = {
      pkpEthAddress,
      appId,
      appVersion,
      permittedAt: new Date().toISOString(),
      network: this.network,
    };

    if (existingIndex >= 0) {
      // Update existing permission
      configState.state.pkpAppPermissions[existingIndex] = newPermission;
      console.log(
        chalk.gray(
          `💾 Updated PKP app permission for config ${this.configHash}: PKP ${pkpEthAddress} for app ${appId} v${appVersion}`
        )
      );
    } else {
      // Add new permission
      configState.state.pkpAppPermissions.push(newPermission);
      console.log(
        chalk.gray(
          `💾 Saved PKP app permission for config ${this.configHash}: PKP ${pkpEthAddress} for app ${appId} v${appVersion}`
        )
      );
    }
  }
}
