import { ChainType, WalletMappingInput } from "../types";
import { IAddressTrackerPort } from "../providers/ProviderPorts";
import { IMappingStore } from "./StoreContracts";

const NETWORK_CONFIG: Record<string, { chainType: string }> = {
  eth: { chainType: "ethereum" },
  ethereum: { chainType: "ethereum" },
  base: { chainType: "base" },
  bnb: { chainType: "bsc" },
  bsc: { chainType: "bsc" },
  solana: { chainType: "solana" },
  tron: { chainType: "tron" },
};

export class WalletRegistrationService {
  constructor(
    private mappingStoreService: IMappingStore,
    private addressTracker: IAddressTrackerPort
  ) {}

  async registerWallets(derivationIdentityKey: number, walletsInput: unknown[]): Promise<{
    user_id: number;
    wallets: {
      created: WalletMappingInput[];
      skipped: Array<{ chain_type: string; reason: string; wallet: WalletMappingInput }>;
    };
    mapping: Awaited<ReturnType<IMappingStore["getByUserId"]>>;
    tracking: { tracked: number; skipped: number; overflow: number; enabled: boolean };
  }> {
    const existingMapping = await this.mappingStoreService.getByUserId(derivationIdentityKey);
    const existingByKey = new Map<string, WalletMappingInput>();

    for (const wallet of existingMapping?.wallets || []) {
      existingByKey.set(this.walletKey(wallet), wallet);
    }

    const created: WalletMappingInput[] = [];
    const skipped: Array<{ chain_type: string; reason: string; wallet: WalletMappingInput }> = [];

    for (const walletInput of walletsInput) {
      const wallet = this.normalizeWallet(walletInput);
      const key = this.walletKey(wallet);
      const addressOwner = await this.mappingStoreService.getByTrackedAddress(wallet.address);

      if (addressOwner && addressOwner.userId !== derivationIdentityKey) {
        const error: Error & { statusCode?: number } = new Error(
          `Address ${wallet.address} is already registered to another user`
        );
        error.statusCode = 409;
        throw error;
      }

      const existingWallet = existingByKey.get(key);
      if (existingWallet) {
        if (this.normalizeAddress(existingWallet.address) !== this.normalizeAddress(wallet.address)) {
          const error: Error & { statusCode?: number } = new Error(
            `Chain ${wallet.chain_type} is already registered with a different address`
          );
          error.statusCode = 409;
          throw error;
        }

        skipped.push({
          chain_type: wallet.chain_type,
          reason: "already_provisioned",
          wallet: existingWallet,
        });
        continue;
      }

      created.push(wallet);
      existingByKey.set(key, wallet);
    }

    const mapping = await this.mappingStoreService.upsertUserMapping(derivationIdentityKey, created);
    const tracking = await this.addressTracker.addAddresses(
      created.map((wallet) => ({ user_id: derivationIdentityKey, wallet }))
    );

    return {
      user_id: derivationIdentityKey,
      wallets: {
        created,
        skipped,
      },
      mapping,
      tracking: {
        tracked: tracking.tracked,
        skipped: tracking.skipped,
        overflow: tracking.overflow,
        enabled: this.addressTracker.isEnabled(),
      },
    };
  }

  async removeTrackedAddresses(addresses: string[]): Promise<{ removed: number; skipped: number }> {
    return this.addressTracker.removeAddresses(addresses);
  }

  private normalizeWallet(input: unknown): WalletMappingInput {
    if (!input || typeof input !== "object") {
      throw new Error("Each wallet must be an object");
    }

    const wallet = input as Record<string, unknown>;
    const chain = String(wallet.chain_type || wallet.chainType || "").trim().toLowerCase() as ChainType;
    const address = String(wallet.address || "").trim();
    const walletId = wallet.wallet_id || wallet.walletId || null;
    const derivationIndex = wallet.derivation_index ?? wallet.derivationIndex ?? null;

    if (!chain || !address) {
      throw new Error("Each wallet requires chain_type and address");
    }

    const network = NETWORK_CONFIG[chain] || { chainType: chain };

    return {
      chain_type: network.chainType,
      address,
      wallet_id: typeof walletId === "string" && walletId.trim() ? walletId.trim() : null,
      derivation_index: Number.isInteger(derivationIndex) ? Number(derivationIndex) : null,
      organization_id: this.normalizeNullableString(wallet.organization_id),
      branch_id: this.normalizeNullableString(wallet.branch_id),
      terminal_id: this.normalizeNullableString(wallet.terminal_id),
      payment_id: this.normalizeNullableString(wallet.payment_id),
      supported_asset_id: this.normalizeNullableString(wallet.supported_asset_id),
      network: this.normalizeNullableString(wallet.network),
    };
  }

  private walletKey(wallet: WalletMappingInput): string {
    return wallet.chain_type;
  }

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

  private normalizeNullableString(value: unknown): string | null {
    return typeof value === "string" && value.trim() ? value.trim() : null;
  }
}
