import { Injectable } from '@nestjs/common';
import { InjectDataSource } from '@nestjs/typeorm';
import { DataSource } from 'typeorm';
import { UserPointBalance } from '../../domain/entities/user-point-balance.entity';
import { UserPointTransaction, PointTransactionType } from '../../domain/entities/user-point-transaction.entity';

@Injectable()
export class UserPointRepository {
  constructor(@InjectDataSource() private readonly ds: DataSource) {}

  /**
   * Add a point transaction AND atomically update the balance cache.
   * delta > 0 = earn, delta < 0 = spend.
   * Throws if resulting balance would go below 0.
   */
  async addTransaction(
    userId: string,
    delta: number,
    type: PointTransactionType,
    refId?: string,
    note?: string,
  ): Promise<UserPointTransaction> {
    return this.ds.transaction(async (em) => {
      // Upsert balance row (lock for update)
      await em.query(
        `INSERT INTO public.user_point_balances (user_id) VALUES ($1)
         ON CONFLICT (user_id) DO NOTHING`,
        [userId],
      );
      const bal = await em.query(
        `SELECT balance FROM public.user_point_balances WHERE user_id = $1 FOR UPDATE`,
        [userId],
      );
      const current: number = bal[0]?.balance ?? 0;
      if (delta < 0 && current + delta < 0) {
        throw new Error(`Insufficient points: balance=${current}, requested=${-delta}`);
      }

      // Insert transaction
      const txRows = await em.query(
        `INSERT INTO public.user_point_transactions (user_id, delta, type, ref_id, note)
         VALUES ($1, $2, $3, $4, $5) RETURNING *`,
        [userId, delta, type, refId ?? null, note ?? null],
      );

      // Update balance
      const earnedDelta = delta > 0 ? delta : 0;
      const spentDelta  = delta < 0 ? delta : 0;
      await em.query(
        `UPDATE public.user_point_balances
         SET balance      = balance + $2,
             total_earned = total_earned + $3,
             total_spent  = total_spent + $4,
             updated_at   = NOW()
         WHERE user_id = $1`,
        [userId, delta, earnedDelta, spentDelta],
      );

      return this.mapTx(txRows[0]);
    });
  }

  async getBalance(userId: string): Promise<UserPointBalance> {
    const rows = await this.ds.query(
      `SELECT * FROM public.user_point_balances WHERE user_id = $1`,
      [userId],
    );
    if (rows[0]) return this.mapBal(rows[0]);
    // No row yet — return zeroes
    return { userId, balance: 0, totalEarned: 0, totalSpent: 0, updatedAt: new Date() };
  }

  async getTransactions(
    userId: string,
    limit = 20,
    offset = 0,
  ): Promise<{ items: UserPointTransaction[]; total: number }> {
    const [rows, count] = await Promise.all([
      this.ds.query(
        `SELECT * FROM public.user_point_transactions
         WHERE user_id = $1 ORDER BY created_at DESC LIMIT $2 OFFSET $3`,
        [userId, limit, offset],
      ),
      this.ds.query(
        `SELECT COUNT(*)::int AS total FROM public.user_point_transactions WHERE user_id = $1`,
        [userId],
      ),
    ]);
    return { items: rows.map((r: any) => this.mapTx(r)), total: count[0]?.total ?? 0 };
  }

  /** Admin: list all point transactions across all users (paginated). */
  async getAllTransactions(
    limit = 50,
    offset = 0,
  ): Promise<{ items: UserPointTransaction[]; total: number }> {
    const [rows, count] = await Promise.all([
      this.ds.query(
        `SELECT * FROM public.user_point_transactions ORDER BY created_at DESC LIMIT $1 OFFSET $2`,
        [limit, offset],
      ),
      this.ds.query(
        `SELECT COUNT(*)::int AS total FROM public.user_point_transactions`,
      ),
    ]);
    return { items: rows.map((r: any) => this.mapTx(r)), total: count[0]?.total ?? 0 };
  }

  /** Admin: get all user balance summaries (paginated). */
  async getAllBalances(
    limit = 50,
    offset = 0,
  ): Promise<{ items: UserPointBalance[]; total: number }> {
    const [rows, count] = await Promise.all([
      this.ds.query(
        `SELECT * FROM public.user_point_balances ORDER BY balance DESC LIMIT $1 OFFSET $2`,
        [limit, offset],
      ),
      this.ds.query(
        `SELECT COUNT(*)::int AS total FROM public.user_point_balances`,
      ),
    ]);
    return { items: rows.map((r: any) => this.mapBal(r)), total: count[0]?.total ?? 0 };
  }

  private mapTx(r: any): UserPointTransaction {
    return {
      id: r.id,
      userId: r.user_id,
      delta: r.delta,
      type: r.type,
      refId: r.ref_id,
      note: r.note,
      createdAt: r.created_at,
    };
  }

  private mapBal(r: any): UserPointBalance {
    return {
      userId: r.user_id,
      balance: r.balance,
      totalEarned: r.total_earned,
      totalSpent: r.total_spent,
      updatedAt: r.updated_at,
    };
  }
}
