/**
 * Repository for provisioning-related database operations
 * Uses two databases:
 * - DB (cloud-instances-db): server specs, pricing, providers
 * - USER_DB (telegram-conversations): users, deposits, orders
 */

import type { TelegramUser, UserDeposit, ServerOrder, PricingWithProvider } from '../types';

export class ProvisioningRepository {
  constructor(
    private db: D1Database,        // cloud-instances-db
    private userDb: D1Database     // telegram-conversations
  ) {}

  // ============================================
  // User Operations (telegram-conversations.users)
  // ============================================

  async getUserByTelegramId(telegramId: string): Promise<TelegramUser | null> {
    const result = await this.userDb
      .prepare('SELECT * FROM users WHERE telegram_id = ?')
      .bind(telegramId)
      .first();

    return result as unknown as TelegramUser | null;
  }

  async getUserById(userId: number): Promise<TelegramUser | null> {
    const result = await this.userDb
      .prepare('SELECT * FROM users WHERE id = ?')
      .bind(userId)
      .first();

    return result as unknown as TelegramUser | null;
  }

  // ============================================
  // Deposit Operations (telegram-conversations.user_deposits)
  // Balance is in KRW (원), INTEGER type
  // ============================================

  async getUserDeposit(userId: number): Promise<UserDeposit | null> {
    const result = await this.userDb
      .prepare('SELECT * FROM user_deposits WHERE user_id = ?')
      .bind(userId)
      .first();

    return result as unknown as UserDeposit | null;
  }

  async getBalance(userId: number): Promise<number> {
    const deposit = await this.getUserDeposit(userId);
    return deposit?.balance ?? 0;
  }

  async deductBalance(userId: number, amount: number): Promise<boolean> {
    // Atomic balance deduction - prevents race condition
    const result = await this.userDb
      .prepare(
        `UPDATE user_deposits
         SET balance = balance - ?, updated_at = datetime('now')
         WHERE user_id = ? AND balance >= ?
         RETURNING balance`
      )
      .bind(amount, userId, amount)
      .first();

    // If no row returned, balance was insufficient
    return result !== null;
  }

  async refundBalance(userId: number, amount: number): Promise<void> {
    await this.userDb
      .prepare(
        `UPDATE user_deposits
         SET balance = balance + ?, updated_at = datetime('now')
         WHERE user_id = ?`
      )
      .bind(amount, userId)
      .run();
  }

  // ============================================
  // Server Order Operations (telegram-conversations.server_orders)
  // ============================================

  async createServerOrder(
    userId: number,
    specId: number,
    region: string,
    pricePaid: number,
    label: string | null,
    image: string | null
  ): Promise<ServerOrder> {
    const result = await this.userDb
      .prepare(
        `INSERT INTO server_orders
         (user_id, spec_id, status, region, price_paid, label, image, billing_type)
         VALUES (?, ?, 'pending', ?, ?, ?, ?, 'monthly')
         RETURNING *`
      )
      .bind(userId, specId, region, pricePaid, label, image)
      .first();

    return result as unknown as ServerOrder;
  }

  async updateOrderStatus(
    orderId: number,
    status: ServerOrder['status'],
    errorMessage?: string
  ): Promise<void> {
    if (errorMessage) {
      await this.userDb
        .prepare(
          `UPDATE server_orders
           SET status = ?, error_message = ?, updated_at = datetime('now')
           WHERE id = ?`
        )
        .bind(status, errorMessage, orderId)
        .run();
    } else {
      await this.userDb
        .prepare(
          `UPDATE server_orders
           SET status = ?, updated_at = datetime('now')
           WHERE id = ?`
        )
        .bind(status, orderId)
        .run();
    }
  }

  async updateOrderRootPassword(orderId: number, rootPassword: string): Promise<void> {
    await this.userDb
      .prepare(
        `UPDATE server_orders
         SET root_password = ?, updated_at = datetime('now')
         WHERE id = ?`
      )
      .bind(rootPassword, orderId)
      .run();
  }

  async updateOrderProviderInfo(
    orderId: number,
    providerInstanceId: string,
    ipAddress: string | null,
    rootPassword: string
  ): Promise<void> {
    await this.userDb
      .prepare(
        `UPDATE server_orders
         SET provider_instance_id = ?,
             ip_address = ?,
             root_password = ?,
             status = 'active',
             provisioned_at = datetime('now'),
             updated_at = datetime('now')
         WHERE id = ?`
      )
      .bind(providerInstanceId, ipAddress, rootPassword, orderId)
      .run();
  }

  async getOrderById(orderId: number): Promise<ServerOrder | null> {
    const result = await this.userDb
      .prepare('SELECT * FROM server_orders WHERE id = ?')
      .bind(orderId)
      .first();

    return result as unknown as ServerOrder | null;
  }

  async getOrdersByUserId(userId: number, limit: number = 20): Promise<ServerOrder[]> {
    const result = await this.userDb
      .prepare(
        'SELECT * FROM server_orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?'
      )
      .bind(userId, limit)
      .all();

    return result.results as unknown as ServerOrder[];
  }

  // ============================================
  // Pricing Lookup (cloud-instances-db)
  // Uses anvil_pricing, anvil_instances, anvil_regions
  // ============================================

  async getPricingWithProvider(pricingId: number): Promise<PricingWithProvider | null> {
    const result = await this.db
      .prepare(
        `SELECT
           ap.id as pricing_id,
           0 as provider_id,
           'Anvil' as provider_name,
           'https://api.anvil.cloud' as api_base_url,
           ai.name as instance_id,
           ai.display_name as instance_name,
           ar.name as region_code,
           ar.display_name as region_name,
           ap.monthly_price,
           ai.vcpus as vcpu,
           CAST(ai.memory_gb * 1024 AS INTEGER) as memory_mb,
           ai.disk_gb as storage_gb
         FROM anvil_pricing ap
         JOIN anvil_instances ai ON ap.anvil_instance_id = ai.id
         JOIN anvil_regions ar ON ap.anvil_region_id = ar.id
         WHERE ap.id = ? AND ai.active = 1 AND ar.active = 1`
      )
      .bind(pricingId)
      .first();

    return result as unknown as PricingWithProvider | null;
  }

  // ============================================
  // Atomic Operations (balance deduction + order creation)
  // ============================================

  /**
   * Deduct balance and create order atomically
   * Returns order ID on success, null on insufficient balance
   */
  async createOrderWithPayment(
    userId: number,
    specId: number,
    region: string,
    priceKrw: number,
    label: string | null,
    image: string | null
  ): Promise<{ orderId: number | null; error?: string }> {
    try {
      // Step 1: Check and deduct balance
      const deducted = await this.deductBalance(userId, priceKrw);
      if (!deducted) {
        return { orderId: null, error: 'INSUFFICIENT_BALANCE' };
      }

      // Step 2: Create order
      const order = await this.createServerOrder(
        userId,
        specId,
        region,
        priceKrw,
        label,
        image
      );

      return { orderId: order.id };
    } catch (error) {
      // Attempt to refund on error
      try {
        await this.refundBalance(userId, priceKrw);
      } catch {
        // Log but don't throw - manual intervention needed
        console.error('[ProvisioningRepository] Failed to refund on error:', error);
      }
      return { orderId: null, error: 'ORDER_CREATION_FAILED' };
    }
  }

  /**
   * Refund balance and update order status on provisioning failure
   */
  async rollbackOrder(orderId: number, userId: number, amount: number, errorMessage: string): Promise<void> {
    await this.refundBalance(userId, amount);
    await this.updateOrderStatus(orderId, 'failed', errorMessage);
  }
}
