import { randomUUID } from 'crypto';
import type {
  BasketAllocation,
  BasketMandate,
  BtcDeposit,
  BridgeWithdrawal,
  CreateBasketMandate,
  CreateBridgeWithdrawal,
  CreateBtcDeposit,
  Holding,
  Rebalance,
} from '@the-order/schemas';

const BTC_CONFIRMATIONS_REQUIRED = 6;
const DEFAULT_ALLOCATION: BasketAllocation = { symbol: 'cBTC', targetWeightBps: 10_000 };
const BTC_BRIDGE_SYMBOL = 'cWBTC';
const BECH32_ALPHABET = '023456789acdefghjklmnpqrstuvwxyz';

function nowIso(): string {
  return new Date().toISOString();
}

function sumWeights(allocations: BasketAllocation[]): number {
  return allocations.reduce((total, allocation) => total + allocation.targetWeightBps, 0);
}

function toPseudoBitcoinAddress(seed: string): string {
  const normalized = seed.replace(/[^a-f0-9]/gi, '').toLowerCase() || 'cb7c138';
  let out = 'bc1q';
  for (let index = 0; index < 32; index += 1) {
    const source = normalized[index % normalized.length] ?? '0';
    const value = parseInt(source, 16);
    out += Number.isNaN(value) ? 'q' : BECH32_ALPHABET[value];
  }
  return out;
}

function clone<T>(value: T): T {
  return JSON.parse(JSON.stringify(value)) as T;
}

export class JewelryBoxStore {
  private readonly baskets = new Map<string, BasketMandate>();
  private readonly deposits = new Map<string, BtcDeposit>();
  private readonly rebalances = new Map<string, Rebalance>();
  private readonly withdrawals = new Map<string, BridgeWithdrawal>();

  createBasket(input: CreateBasketMandate): BasketMandate {
    const allocations = input.allocations.length > 0 ? input.allocations : [DEFAULT_ALLOCATION];
    if (sumWeights(allocations) !== 10_000) {
      throw new Error('Basket allocations must sum to exactly 10,000 bps');
    }

    const timestamp = nowIso();
    const basket: BasketMandate = {
      id: randomUUID(),
      clientId: input.clientId,
      mandateName: input.mandateName,
      chain138VaultAddress: input.chain138VaultAddress,
      baseAssetSymbol: input.baseAssetSymbol || 'cBTC',
      status: 'active',
      allocations: clone(allocations),
      createdAt: timestamp,
      updatedAt: timestamp,
    };

    this.baskets.set(basket.id, basket);
    this.seedRebalancePlan(basket);
    return clone(basket);
  }

  getBasket(id: string): BasketMandate | null {
    const basket = this.baskets.get(id);
    return basket ? clone(basket) : null;
  }

  createDeposit(input: CreateBtcDeposit): { deposit: BtcDeposit; basket: BasketMandate; createdBasket: boolean } {
    let basket = input.basketId ? this.baskets.get(input.basketId) ?? null : null;
    let createdBasket = false;

    if (input.basketId && !basket) {
      throw new Error(`Unknown basket: ${input.basketId}`);
    }

    if (!basket) {
      basket = this.createBasket({
        clientId: input.clientId,
        mandateName: input.mandateName || `BTC jewelry box ${input.clientId}`,
        chain138VaultAddress: input.chain138VaultAddress,
        allocations: input.allocations && input.allocations.length > 0 ? input.allocations : [DEFAULT_ALLOCATION],
        baseAssetSymbol: 'cBTC',
      });
      createdBasket = true;
    } else {
      if (basket.clientId !== input.clientId) {
        throw new Error('Basket client does not match deposit client');
      }
      if (basket.chain138VaultAddress !== input.chain138VaultAddress) {
        throw new Error('Basket vault address does not match deposit vault address');
      }
    }

    const timestamp = nowIso();
    const depositId = randomUUID();
    const deposit: BtcDeposit = {
      id: depositId,
      clientId: input.clientId,
      basketId: basket.id,
      chain138VaultAddress: basket.chain138VaultAddress,
      depositAddress: toPseudoBitcoinAddress(depositId),
      expectedAmountSats: input.expectedAmountSats,
      confirmationsRequired: BTC_CONFIRMATIONS_REQUIRED,
      currentConfirmations: 0,
      status: 'instruction_created',
      createdAt: timestamp,
      updatedAt: timestamp,
    };

    this.deposits.set(deposit.id, deposit);
    return { deposit: clone(deposit), basket: clone(basket), createdBasket };
  }

  getDeposit(id: string): BtcDeposit | null {
    const deposit = this.deposits.get(id);
    return deposit ? clone(deposit) : null;
  }

  listHoldings(filters: { clientId?: string; basketId?: string }): Holding[] {
    const baskets = Array.from(this.baskets.values()).filter((basket) => {
      if (filters.clientId && basket.clientId !== filters.clientId) {
        return false;
      }
      if (filters.basketId && basket.id !== filters.basketId) {
        return false;
      }
      return true;
    });

    return baskets.flatMap((basket) => {
      const fundedSats = Array.from(this.deposits.values())
        .filter((deposit) => deposit.basketId === basket.id && deposit.status === 'minted')
        .reduce((total, deposit) => total + (deposit.expectedAmountSats ?? 0), 0);

      return basket.allocations.map((allocation: BasketAllocation) => ({
        clientId: basket.clientId,
        basketId: basket.id,
        symbol: allocation.symbol,
        allocationWeightBps: allocation.targetWeightBps,
        bookValueSats: Math.floor((fundedSats * allocation.targetWeightBps) / 10_000),
        status: fundedSats > 0 ? 'funded' : 'pending_funding',
        updatedAt: basket.updatedAt,
      }));
    });
  }

  listRebalances(filters: { clientId?: string; basketId?: string }): Rebalance[] {
    return Array.from(this.rebalances.values())
      .filter((rebalance) => {
        if (filters.clientId && rebalance.clientId !== filters.clientId) {
          return false;
        }
        if (filters.basketId && rebalance.basketId !== filters.basketId) {
          return false;
        }
        return true;
      })
      .map((rebalance) => clone(rebalance));
  }

  createBridgeWithdrawal(input: CreateBridgeWithdrawal): BridgeWithdrawal {
    if (!this.baskets.has(input.basketId)) {
      throw new Error(`Unknown basket: ${input.basketId}`);
    }

    const timestamp = nowIso();
    const withdrawal: BridgeWithdrawal = {
      id: randomUUID(),
      clientId: input.clientId,
      basketId: input.basketId,
      sourceSymbol: input.sourceSymbol || 'cBTC',
      destinationSymbol: input.destinationSymbol || BTC_BRIDGE_SYMBOL,
      destinationChainId: input.destinationChainId,
      destinationAddress: input.destinationAddress,
      amount: input.amount,
      status: 'pending',
      createdAt: timestamp,
      updatedAt: timestamp,
    };

    this.withdrawals.set(withdrawal.id, withdrawal);
    return clone(withdrawal);
  }

  markDepositObserved(
    depositId: string,
    observedTxId: string,
    confirmations: number,
    expectedAmountSats?: number,
  ): BtcDeposit {
    const deposit = this.deposits.get(depositId);
    if (!deposit) {
      throw new Error(`Unknown BTC deposit: ${depositId}`);
    }

    const nextConfirmations = Math.max(0, confirmations);
    deposit.observedTxId = observedTxId;
    deposit.currentConfirmations = nextConfirmations;
    if (expectedAmountSats !== undefined) {
      deposit.expectedAmountSats = expectedAmountSats;
    }
    deposit.status =
      nextConfirmations >= deposit.confirmationsRequired ? 'confirmed' : 'pending_confirmations';
    deposit.updatedAt = nowIso();

    return clone(deposit);
  }

  markDepositMinted(depositId: string): BtcDeposit {
    const deposit = this.deposits.get(depositId);
    if (!deposit) {
      throw new Error(`Unknown BTC deposit: ${depositId}`);
    }

    deposit.currentConfirmations = Math.max(deposit.currentConfirmations, deposit.confirmationsRequired);
    deposit.status = 'minted';
    deposit.updatedAt = nowIso();

    const basket = this.baskets.get(deposit.basketId);
    if (basket) {
      basket.updatedAt = deposit.updatedAt;
      if (basket.allocations.some((allocation: BasketAllocation) => allocation.symbol !== 'cBTC')) {
        this.promoteRebalancePlan(basket);
      }
    }

    return clone(deposit);
  }

  freezeDeposit(depositId: string, reason: string): BtcDeposit {
    const deposit = this.deposits.get(depositId);
    if (!deposit) {
      throw new Error(`Unknown BTC deposit: ${depositId}`);
    }
    deposit.status = 'frozen';
    deposit.freezeReason = reason;
    deposit.updatedAt = nowIso();
    return clone(deposit);
  }

  private seedRebalancePlan(basket: BasketMandate): void {
    const targetSymbols = basket.allocations
      .map((allocation: BasketAllocation) => allocation.symbol)
      .filter((symbol: string) => symbol !== 'cBTC');

    if (targetSymbols.length === 0) {
      return;
    }

    const timestamp = nowIso();
    const rebalance: Rebalance = {
      id: randomUUID(),
      clientId: basket.clientId,
      basketId: basket.id,
      sourceSymbol: 'cBTC',
      targetSymbols,
      reason: 'Awaiting funded BTC deposit before Chain 138 rebalance',
      status: 'planned',
      createdAt: timestamp,
      updatedAt: timestamp,
    };

    this.rebalances.set(rebalance.id, rebalance);
  }

  private promoteRebalancePlan(basket: BasketMandate): void {
    for (const rebalance of this.rebalances.values()) {
      if (rebalance.basketId === basket.id && rebalance.status === 'planned') {
        rebalance.status = 'queued';
        rebalance.updatedAt = nowIso();
      }
    }
  }
}

export function createDefaultBridgeWithdrawal(
  input: Omit<CreateBridgeWithdrawal, 'destinationSymbol' | 'sourceSymbol'> & {
    sourceSymbol?: string;
    destinationSymbol?: string;
  },
): CreateBridgeWithdrawal {
  return {
    ...input,
    sourceSymbol: input.sourceSymbol || 'cBTC',
    destinationSymbol: input.destinationSymbol || BTC_BRIDGE_SYMBOL,
  };
}
