import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateSaleDto } from './dto/create-sale.dto';
import { UpdateSaleDto } from './dto/update-sale.dto';
import { FindSalesDto } from './dto/find-sales.dto';
import { Prisma, SaleStatus } from '@prisma/client';

export interface PaginatedResult<T> {
  data: T[];
  nextCursor: string | null;
  hasMore: boolean;
}

export interface DebtsSummary {
  totalAmount: number;
  count: number;
}

const SALE_INCLUDE = {
  trainer: { select: { id: true, firstName: true, lastName: true } },
  client: {
    select: { id: true, firstName: true, lastName: true, phone: true },
  },
  service: { select: { id: true, name: true, price: true } },
  package: { select: { id: true, name: true, price: true } },
};

@Injectable()
export class SalesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly eventEmitter: EventEmitter2,
  ) {}

  /** Find all sales with cursor-based pagination and filters. */
  async findAll(
    clubId: string,
    params: FindSalesDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, trainerId, clientId, status, dateFrom, dateTo } = params;

    const where: Prisma.SaleWhereInput = {
      clubId,
      ...(trainerId ? { trainerId } : {}),
      ...(clientId ? { clientId } : {}),
      ...(status ? { status } : {}),
      ...this.buildDateFilter(dateFrom, dateTo),
    };

    return this.paginatedQuery(where, cursor, limit);
  }

  /** Find a single sale by ID with all relations. */
  async findById(clubId: string, id: string) {
    const sale = await this.prisma.sale.findFirst({
      where: { id, clubId },
      include: SALE_INCLUDE,
    });

    if (!sale) {
      throw new NotFoundException(`Sale with id ${id} not found`);
    }
    return sale;
  }

  /**
   * Create a new sale. Validates client exists in club.
   * If packageId provided and amount is 0, auto-fills from package price.
   */
  async create(clubId: string, trainerId: string, dto: CreateSaleDto) {
    await this.ensureClientExists(clubId, dto.clientId);

    let amount = dto.amount;

    if (dto.packageId && (!amount || amount === 0)) {
      const pkg = await this.prisma.package.findFirst({
        where: { id: dto.packageId, clubId },
      });
      if (!pkg) {
        throw new NotFoundException(`Package with id ${dto.packageId} not found`);
      }
      amount = Number(pkg.price);
    }

    if (dto.serviceId) {
      const service = await this.prisma.service.findFirst({
        where: { id: dto.serviceId, clubId },
      });
      if (!service) {
        throw new NotFoundException(`Service with id ${dto.serviceId} not found`);
      }
    }

    const sale = await this.prisma.sale.create({
      data: {
        clubId,
        trainerId,
        clientId: dto.clientId,
        serviceId: dto.serviceId,
        packageId: dto.packageId,
        amount,
        description: dto.description,
      },
      include: SALE_INCLUDE,
    });

    this.eventEmitter.emit('sale.created', { clubId, data: sale });

    return sale;
  }

  /** Update a PENDING sale only. */
  async update(clubId: string, id: string, dto: UpdateSaleDto) {
    const sale = await this.ensureSaleExists(clubId, id);
    this.assertStatus(sale.status, SaleStatus.PENDING, 'update');

    return this.prisma.sale.update({
      where: { id },
      data: {
        ...(dto.amount !== undefined ? { amount: dto.amount } : {}),
        ...(dto.description !== undefined ? { description: dto.description } : {}),
        ...(dto.serviceId !== undefined ? { serviceId: dto.serviceId } : {}),
        ...(dto.packageId !== undefined ? { packageId: dto.packageId } : {}),
      },
      include: SALE_INCLUDE,
    });
  }

  /** Cancel a PENDING sale. */
  async cancel(clubId: string, id: string) {
    const sale = await this.ensureSaleExists(clubId, id);
    this.assertStatus(sale.status, SaleStatus.PENDING, 'cancel');

    return this.prisma.sale.update({
      where: { id },
      data: { status: SaleStatus.CANCELLED },
      include: SALE_INCLUDE,
    });
  }

  /** Mark a PENDING sale as PAID and set paidAt timestamp. */
  async markAsPaid(clubId: string, id: string) {
    const sale = await this.ensureSaleExists(clubId, id);
    this.assertStatus(sale.status, SaleStatus.PENDING, 'mark as paid');

    const paid = await this.prisma.sale.update({
      where: { id },
      data: { status: SaleStatus.PAID, paidAt: new Date() },
      include: SALE_INCLUDE,
    });

    this.eventEmitter.emit('sale.paid', { clubId, data: paid });

    return paid;
  }

  /** Refund a PAID sale. */
  async refund(clubId: string, id: string) {
    const sale = await this.ensureSaleExists(clubId, id);
    this.assertStatus(sale.status, SaleStatus.PAID, 'refund');

    return this.prisma.sale.update({
      where: { id },
      data: { status: SaleStatus.REFUNDED },
      include: SALE_INCLUDE,
    });
  }

  /** Find all PENDING sales (debts) with cursor-based pagination. */
  async getDebts(
    clubId: string,
    params: FindSalesDto,
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, trainerId, clientId, dateFrom, dateTo } = params;

    const where: Prisma.SaleWhereInput = {
      clubId,
      status: SaleStatus.PENDING,
      ...(trainerId ? { trainerId } : {}),
      ...(clientId ? { clientId } : {}),
      ...this.buildDateFilter(dateFrom, dateTo),
    };

    return this.paginatedQuery(where, cursor, limit);
  }

  /** Get debt summary: total unpaid amount and count. */
  async getDebtsSummary(
    clubId: string,
    trainerId?: string,
  ): Promise<DebtsSummary> {
    const where: Prisma.SaleWhereInput = {
      clubId,
      status: SaleStatus.PENDING,
      ...(trainerId ? { trainerId } : {}),
    };

    const result = await this.prisma.sale.aggregate({
      where,
      _sum: { amount: true },
      _count: { id: true },
    });

    return {
      totalAmount: Number(result._sum.amount ?? 0),
      count: result._count.id,
    };
  }

  /** Reusable cursor-based pagination query. */
  private async paginatedQuery(
    where: Prisma.SaleWhereInput,
    cursor?: string,
    limit = 20,
  ): Promise<PaginatedResult<any>> {
    const sales = await this.prisma.sale.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
      include: SALE_INCLUDE,
    });

    const hasMore = sales.length > limit;
    const data = hasMore ? sales.slice(0, -1) : sales;

    return {
      data,
      nextCursor: hasMore ? data[data.length - 1].id : null,
      hasMore,
    };
  }

  /** Build date range filter for createdAt. */
  private buildDateFilter(dateFrom?: string, dateTo?: string): Prisma.SaleWhereInput {
    if (!dateFrom && !dateTo) return {};

    const createdAt: Prisma.DateTimeFilter = {};
    if (dateFrom) createdAt.gte = new Date(dateFrom);
    if (dateTo) createdAt.lte = new Date(dateTo);

    return { createdAt };
  }

  /** Assert that a sale has the expected status, or throw. */
  private assertStatus(
    current: SaleStatus,
    expected: SaleStatus,
    action: string,
  ): void {
    if (current !== expected) {
      throw new BadRequestException(
        `Cannot ${action} sale with status ${current}. Only ${expected} sales are allowed.`,
      );
    }
  }

  /** Ensure a sale exists within the club. Returns sale for status checks. */
  private async ensureSaleExists(clubId: string, id: string) {
    const sale = await this.prisma.sale.findFirst({ where: { id, clubId } });
    if (!sale) {
      throw new NotFoundException(`Sale with id ${id} not found`);
    }
    return sale;
  }

  /** Ensure a client exists within the club. */
  private async ensureClientExists(clubId: string, clientId: string): Promise<void> {
    const count = await this.prisma.client.count({ where: { id: clientId, clubId } });
    if (count === 0) {
      throw new NotFoundException(`Client with id ${clientId} not found`);
    }
  }
}
