import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { dirname } from "path";
import { UserMapping, WalletMappingInput } from "../types";
import { IMappingStore } from "./StoreContracts";

interface MappingStoreFile {
  users: Record<string, UserMapping>;
  address_index?: Record<string, { user_id: number; wallet_key: string }>;
}

export class MappingStoreService implements IMappingStore {
  constructor(private filePath: string) {
    this.ensureStore();
  }

  private ensureStore(): void {
    if (!existsSync(dirname(this.filePath))) {
      mkdirSync(dirname(this.filePath), { recursive: true });
    }

    if (!existsSync(this.filePath)) {
      this.writeStore({ users: {} });
    }
  }

  private readStore(): MappingStoreFile {
    const store = JSON.parse(readFileSync(this.filePath, "utf8")) as MappingStoreFile;
    return this.withIndexes(store);
  }

  private writeStore(data: MappingStoreFile): void {
    writeFileSync(this.filePath, JSON.stringify(this.withIndexes(data), null, 2), "utf8");
  }

  async getByUserId(userId: number): Promise<UserMapping | null> {
    const store = this.readStore();
    return store.users[String(userId)] || null;
  }

  async upsertUserMapping(userId: number, wallets: WalletMappingInput[]): Promise<UserMapping> {
    const store = this.readStore();
    const key = String(userId);
    const existing = store.users[key] || {
      user_id: userId,
      wallets: [],
    };

    const byChain = new Map<string, WalletMappingInput>();
    for (const wallet of existing.wallets) {
      byChain.set(this.walletKey(wallet), wallet);
    }
    for (const wallet of wallets) {
      byChain.set(this.walletKey(wallet), wallet);
    }

    const updated: UserMapping = {
      user_id: userId,
      wallets: Array.from(byChain.values()),
    };

    store.users[key] = updated;
    this.writeStore(store);
    return updated;
  }

  async getByTrackedAddress(address: string): Promise<{ userId: number; wallet: WalletMappingInput } | null> {
    const store = this.readStore();
    const normalized = this.normalizeAddress(address);
    const indexed = store.address_index?.[normalized];
    if (!indexed) {
      return null;
    }

    const mapping = store.users[String(indexed.user_id)];
    const wallet = mapping?.wallets.find((item) => this.walletKey(item) === indexed.wallet_key);
    return mapping && wallet ? { userId: mapping.user_id, wallet } : null;
  }

  async getWalletsByChainType(chainType: string): Promise<Array<{ user_id: number; wallet: WalletMappingInput }>> {
    const store = this.readStore();
    const normalizedChainType = String(chainType || "").trim().toLowerCase();
    const result: Array<{ user_id: number; wallet: WalletMappingInput }> = [];

    for (const mapping of Object.values(store.users || {})) {
      for (const wallet of mapping.wallets || []) {
        if (wallet.chain_type.toLowerCase() === normalizedChainType) {
          result.push({ user_id: mapping.user_id, wallet });
        }
      }
    }

    return result;
  }

  private withIndexes(store: MappingStoreFile): MappingStoreFile {
    const addressIndex: Record<string, { user_id: number; wallet_key: string }> = {};

    for (const mapping of Object.values(store.users || {})) {
      for (const wallet of mapping.wallets || []) {
        addressIndex[this.normalizeAddress(wallet.address)] = {
          user_id: mapping.user_id,
          wallet_key: this.walletKey(wallet),
        };
      }
    }

    return {
      users: store.users || {},
      address_index: addressIndex,
    };
  }

  private walletKey(wallet: WalletMappingInput): string {
    return wallet.wallet_id && wallet.wallet_id.trim()
      ? `${wallet.chain_type}:${wallet.wallet_id.trim()}`
      : wallet.chain_type;
  }

  private normalizeAddress(address: string): string {
    const trimmed = address.trim();
    return trimmed.startsWith("0x") || trimmed.startsWith("0X") ? trimmed.toLowerCase() : trimmed;
  }
}
