import { Injectable } from '@nestjs/common';
import { PrismaService } from '../../prisma/prisma.service';
import { Prisma, FunnelStage, TrainingStatus, SaleStatus } from '@prisma/client';

// ---------------------------------------------------------------------------
// Response interfaces
// ---------------------------------------------------------------------------

export interface DashboardSummary {
  totalTrainings: number;
  completedTrainings: number;
  cancelledTrainings: number;
  totalClients: number;
  newClients: number;
  totalSalesAmount: number;
  paidSalesAmount: number;
  upcomingTrainings: number;
  sleepingClients: number;
}

export interface FunnelConversion {
  newToAssigned: number;
  assignedToCompleted: number;
  completedToRegular: number;
  totalNew: number;
  totalAssigned: number;
  totalCompleted: number;
  totalRegular: number;
  perTrainer: TrainerFunnelBreakdown[];
}

export interface TrainerFunnelBreakdown {
  trainerId: string;
  trainerName: string;
  totalNew: number;
  totalAssigned: number;
  totalCompleted: number;
  totalRegular: number;
  newToAssigned: number;
  assignedToCompleted: number;
  completedToRegular: number;
}

export interface RatingEntry {
  userId: string;
  firstName: string;
  lastName: string;
  rank: number;
  score: number;
  totalTrainings: number;
  totalSalesAmount: number;
  totalClients: number;
  newClients: number;
  conversionNewToAssigned: number | null;
  conversionAssignedToCompleted: number | null;
  conversionCompletedToRegular: number | null;
}

export interface TrainerDetailedStats {
  trainerId: string;
  trainings: {
    total: number;
    planned: number;
    confirmed: number;
    completed: number;
    paid: number;
    notPaid: number;
    noShow: number;
    cancelled: number;
  };
  sales: {
    totalAmount: number;
    paidAmount: number;
    pendingAmount: number;
    count: number;
  };
  funnel: {
    newToAssigned: number;
    assignedToCompleted: number;
    completedToRegular: number;
    totalNew: number;
    totalAssigned: number;
    totalCompleted: number;
    totalRegular: number;
  };
  clientCount: number;
}

// ---------------------------------------------------------------------------
// Service
// ---------------------------------------------------------------------------

@Injectable()
export class StatsService {
  constructor(private readonly prisma: PrismaService) {}

  /**
   * Dashboard KPIs: training counts, client counts, sales totals,
   * upcoming trainings, and sleeping clients.
   */
  async getSummary(
    clubId: string,
    trainerId?: string,
    dateFrom?: string,
    dateTo?: string,
  ): Promise<DashboardSummary> {
    const trainingWhere = this.buildTrainingWhere(
      clubId, trainerId, dateFrom, dateTo,
    );
    const saleWhere = this.buildSaleWhere(clubId, trainerId, dateFrom, dateTo);
    const clientWhere = this.buildClientWhere(clubId, trainerId);

    const now = new Date();
    const sevenDaysLater = new Date(now);
    sevenDaysLater.setDate(sevenDaysLater.getDate() + 7);

    const thirtyDaysAgo = new Date(now);
    thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);

    const newClientsDateFilter: Prisma.ClientWhereInput =
      dateFrom || dateTo
        ? {
            createdAt: {
              ...(dateFrom ? { gte: new Date(dateFrom) } : {}),
              ...(dateTo ? { lte: new Date(dateTo) } : {}),
            },
          }
        : {};

    const [
      totalTrainings,
      completedTrainings,
      cancelledTrainings,
      totalClients,
      newClients,
      salesAgg,
      paidSalesAgg,
      upcomingTrainings,
      sleepingClients,
    ] = await Promise.all([
      this.prisma.training.count({ where: trainingWhere }),
      this.prisma.training.count({
        where: { ...trainingWhere, status: TrainingStatus.COMPLETED },
      }),
      this.prisma.training.count({
        where: { ...trainingWhere, status: TrainingStatus.CANCELLED },
      }),
      this.prisma.client.count({ where: clientWhere }),
      this.prisma.client.count({
        where: { ...clientWhere, ...newClientsDateFilter },
      }),
      this.prisma.sale.aggregate({
        where: saleWhere,
        _sum: { amount: true },
      }),
      this.prisma.sale.aggregate({
        where: { ...saleWhere, status: SaleStatus.PAID },
        _sum: { amount: true },
      }),
      this.prisma.training.count({
        where: {
          clubId,
          ...(trainerId ? { trainerId } : {}),
          scheduledAt: { gte: now, lte: sevenDaysLater },
          status: { in: [TrainingStatus.PLANNED, TrainingStatus.CONFIRMED] },
        },
      }),
      this.prisma.client.count({
        where: {
          clubId,
          ...(trainerId ? { assignedTrainerId: trainerId } : {}),
          lastActivityAt: { lt: thirtyDaysAgo },
          memberships: { some: { isActive: true } },
        },
      }),
    ]);

    return {
      totalTrainings,
      completedTrainings,
      cancelledTrainings,
      totalClients,
      newClients,
      totalSalesAmount: Number(salesAgg._sum.amount ?? 0),
      paidSalesAmount: Number(paidSalesAgg._sum.amount ?? 0),
      upcomingTrainings,
      sleepingClients,
    };
  }

  /**
   * Funnel conversion rates with optional per-trainer breakdowns.
   */
  async getFunnelConversion(
    clubId: string,
    trainerId?: string,
    dateFrom?: string,
    dateTo?: string,
  ): Promise<FunnelConversion> {
    const dateFilter = this.buildFunnelDateFilter(clubId, dateFrom, dateTo);
    const trainerFilter = trainerId ? { trainerId } : {};
    const baseWhere = { ...dateFilter, ...trainerFilter };

    const [totalNew, totalAssigned, totalCompleted, totalRegular] =
      await Promise.all([
        this.prisma.funnelEntry.count({
          where: { ...baseWhere, stage: FunnelStage.NEW },
        }),
        this.prisma.funnelEntry.count({
          where: { ...baseWhere, stage: FunnelStage.ASSIGNED },
        }),
        this.prisma.funnelEntry.count({
          where: { ...baseWhere, stage: FunnelStage.COMPLETED },
        }),
        this.prisma.funnelEntry.count({
          where: { ...baseWhere, stage: FunnelStage.REGULAR },
        }),
      ]);

    // Per-trainer breakdown (skip if already filtered to a single trainer)
    const perTrainer = trainerId
      ? []
      : await this.getPerTrainerFunnelBreakdown(clubId, dateFrom, dateTo);

    return {
      newToAssigned: safeDiv(totalAssigned, totalNew),
      assignedToCompleted: safeDiv(totalCompleted, totalAssigned),
      completedToRegular: safeDiv(totalRegular, totalCompleted),
      totalNew,
      totalAssigned,
      totalCompleted,
      totalRegular,
      perTrainer,
    };
  }

  /**
   * Trainer rating list from RatingPeriod table.
   * Falls back to on-the-fly calculation if no snapshots exist.
   */
  async getRating(
    clubId: string,
    period?: string,
  ): Promise<RatingEntry[]> {
    const periodDate = this.resolvePeriodDate(period);
    const periodFilter = periodDate ? { period: { gte: periodDate } } : {};

    const ratingPeriods = await this.prisma.ratingPeriod.findMany({
      where: { clubId, ...periodFilter },
      orderBy: [{ score: 'desc' }, { totalTrainings: 'desc' }],
      include: {
        user: { select: { id: true, firstName: true, lastName: true } },
      },
    });

    if (ratingPeriods.length > 0) {
      return ratingPeriods.map((rp, index) => ({
        userId: rp.userId,
        firstName: rp.user.firstName,
        lastName: rp.user.lastName,
        rank: rp.rank ?? index + 1,
        score: Number(rp.score ?? 0),
        totalTrainings: rp.totalTrainings,
        totalSalesAmount: Number(rp.totalSalesAmount),
        totalClients: rp.totalClients,
        newClients: rp.newClients,
        conversionNewToAssigned: rp.conversionNewToAssigned
          ? Number(rp.conversionNewToAssigned)
          : null,
        conversionAssignedToCompleted: rp.conversionAssignedToCompleted
          ? Number(rp.conversionAssignedToCompleted)
          : null,
        conversionCompletedToRegular: rp.conversionCompletedToRegular
          ? Number(rp.conversionCompletedToRegular)
          : null,
      }));
    }

    return this.calculateRatingOnTheFly(clubId);
  }

  /**
   * Detailed stats for a specific trainer: training counts by status,
   * sales amounts, funnel conversion, client count.
   */
  async getTrainerStats(
    clubId: string,
    trainerId: string,
    dateFrom?: string,
    dateTo?: string,
  ): Promise<TrainerDetailedStats> {
    const trainingWhere = this.buildTrainingWhere(
      clubId, trainerId, dateFrom, dateTo,
    );
    const saleWhere = this.buildSaleWhere(clubId, trainerId, dateFrom, dateTo);
    const funnelDateFilter = this.buildFunnelDateFilter(
      clubId, dateFrom, dateTo,
    );

    const [
      trainingsByStatus,
      salesAgg,
      paidSalesAgg,
      pendingSalesAgg,
      salesCount,
      funnelNew,
      funnelAssigned,
      funnelCompleted,
      funnelRegular,
      clientCount,
    ] = await Promise.all([
      this.prisma.training.groupBy({
        by: ['status'],
        where: trainingWhere,
        _count: { status: true },
      }),
      this.prisma.sale.aggregate({
        where: saleWhere,
        _sum: { amount: true },
      }),
      this.prisma.sale.aggregate({
        where: { ...saleWhere, status: SaleStatus.PAID },
        _sum: { amount: true },
      }),
      this.prisma.sale.aggregate({
        where: { ...saleWhere, status: SaleStatus.PENDING },
        _sum: { amount: true },
      }),
      this.prisma.sale.count({ where: saleWhere }),
      this.prisma.funnelEntry.count({
        where: { ...funnelDateFilter, trainerId, stage: FunnelStage.NEW },
      }),
      this.prisma.funnelEntry.count({
        where: { ...funnelDateFilter, trainerId, stage: FunnelStage.ASSIGNED },
      }),
      this.prisma.funnelEntry.count({
        where: { ...funnelDateFilter, trainerId, stage: FunnelStage.COMPLETED },
      }),
      this.prisma.funnelEntry.count({
        where: { ...funnelDateFilter, trainerId, stage: FunnelStage.REGULAR },
      }),
      this.prisma.client.count({
        where: { clubId, assignedTrainerId: trainerId },
      }),
    ]);

    const statusMap = new Map(
      trainingsByStatus.map((g) => [g.status, g._count.status]),
    );

    return {
      trainerId,
      trainings: {
        total: Array.from(statusMap.values()).reduce((a, b) => a + b, 0),
        planned: statusMap.get(TrainingStatus.PLANNED) ?? 0,
        confirmed: statusMap.get(TrainingStatus.CONFIRMED) ?? 0,
        completed: statusMap.get(TrainingStatus.COMPLETED) ?? 0,
        paid: statusMap.get(TrainingStatus.PAID) ?? 0,
        notPaid: statusMap.get(TrainingStatus.NOT_PAID) ?? 0,
        noShow: statusMap.get(TrainingStatus.NO_SHOW) ?? 0,
        cancelled: statusMap.get(TrainingStatus.CANCELLED) ?? 0,
      },
      sales: {
        totalAmount: Number(salesAgg._sum.amount ?? 0),
        paidAmount: Number(paidSalesAgg._sum.amount ?? 0),
        pendingAmount: Number(pendingSalesAgg._sum.amount ?? 0),
        count: salesCount,
      },
      funnel: {
        newToAssigned: safeDiv(funnelAssigned, funnelNew),
        assignedToCompleted: safeDiv(funnelCompleted, funnelAssigned),
        completedToRegular: safeDiv(funnelRegular, funnelCompleted),
        totalNew: funnelNew,
        totalAssigned: funnelAssigned,
        totalCompleted: funnelCompleted,
        totalRegular: funnelRegular,
      },
      clientCount,
    };
  }

  // ---------------------------------------------------------------------------
  // Private helpers
  // ---------------------------------------------------------------------------

  /**
   * Per-trainer funnel breakdown: group by trainerId for each stage,
   * then merge with trainer names.
   */
  private async getPerTrainerFunnelBreakdown(
    clubId: string,
    dateFrom?: string,
    dateTo?: string,
  ): Promise<TrainerFunnelBreakdown[]> {
    const dateFilter = this.buildFunnelDateFilter(clubId, dateFrom, dateTo);

    const grouped = await this.prisma.funnelEntry.groupBy({
      by: ['trainerId', 'stage'],
      where: { ...dateFilter, trainerId: { not: null } },
      _count: { stage: true },
    });

    // Collect unique trainer IDs
    const trainerIds = [
      ...new Set(grouped.map((g) => g.trainerId).filter(Boolean)),
    ] as string[];

    if (trainerIds.length === 0) return [];

    const trainers = await this.prisma.user.findMany({
      where: { id: { in: trainerIds }, clubId },
      select: { id: true, firstName: true, lastName: true },
    });

    const trainerMap = new Map(trainers.map((t) => [t.id, t]));

    // Build per-trainer stats
    const statsMap = new Map<string, {
      totalNew: number;
      totalAssigned: number;
      totalCompleted: number;
      totalRegular: number;
    }>();

    for (const g of grouped) {
      const tid = g.trainerId!;
      if (!statsMap.has(tid)) {
        statsMap.set(tid, {
          totalNew: 0,
          totalAssigned: 0,
          totalCompleted: 0,
          totalRegular: 0,
        });
      }
      const entry = statsMap.get(tid)!;
      if (g.stage === FunnelStage.NEW) entry.totalNew = g._count.stage;
      if (g.stage === FunnelStage.ASSIGNED) entry.totalAssigned = g._count.stage;
      if (g.stage === FunnelStage.COMPLETED) entry.totalCompleted = g._count.stage;
      if (g.stage === FunnelStage.REGULAR) entry.totalRegular = g._count.stage;
    }

    return trainerIds.map((tid) => {
      const trainer = trainerMap.get(tid);
      const s = statsMap.get(tid)!;
      return {
        trainerId: tid,
        trainerName: trainer
          ? `${trainer.firstName} ${trainer.lastName}`
          : tid,
        totalNew: s.totalNew,
        totalAssigned: s.totalAssigned,
        totalCompleted: s.totalCompleted,
        totalRegular: s.totalRegular,
        newToAssigned: safeDiv(s.totalAssigned, s.totalNew),
        assignedToCompleted: safeDiv(s.totalCompleted, s.totalAssigned),
        completedToRegular: safeDiv(s.totalRegular, s.totalCompleted),
      };
    });
  }

  /**
   * Calculate rating on-the-fly when no RatingPeriod snapshots exist.
   * Uses training count + sales as a simple scoring formula.
   */
  private async calculateRatingOnTheFly(
    clubId: string,
  ): Promise<RatingEntry[]> {
    const trainers = await this.prisma.user.findMany({
      where: { clubId, role: 'TRAINER', isActive: true },
      select: { id: true, firstName: true, lastName: true },
    });

    if (trainers.length === 0) return [];

    const trainerIds = trainers.map((t) => t.id);

    const [trainingCounts, salesAmounts, clientCounts] = await Promise.all([
      this.prisma.training.groupBy({
        by: ['trainerId'],
        where: {
          clubId,
          trainerId: { in: trainerIds },
          status: TrainingStatus.COMPLETED,
        },
        _count: { trainerId: true },
      }),
      this.prisma.sale.groupBy({
        by: ['trainerId'],
        where: {
          clubId,
          trainerId: { in: trainerIds },
          status: SaleStatus.PAID,
        },
        _sum: { amount: true },
      }),
      this.prisma.client.groupBy({
        by: ['assignedTrainerId'],
        where: {
          clubId,
          assignedTrainerId: { in: trainerIds },
        },
        _count: { assignedTrainerId: true },
      }),
    ]);

    const trainingMap = new Map(
      trainingCounts.map((t) => [t.trainerId, t._count.trainerId]),
    );
    const salesMap = new Map(
      salesAmounts.map((s) => [s.trainerId, Number(s._sum.amount ?? 0)]),
    );
    const clientMap = new Map(
      clientCounts.map((c) => [
        c.assignedTrainerId!,
        c._count.assignedTrainerId,
      ]),
    );

    const entries: RatingEntry[] = trainers.map((t) => {
      const totalTrainings = trainingMap.get(t.id) ?? 0;
      const totalSalesAmount = salesMap.get(t.id) ?? 0;
      const totalClients = clientMap.get(t.id) ?? 0;
      // Simple composite score: trainings * 2 + (sales / 1000) + clients
      const score =
        Math.round((totalTrainings * 2 + totalSalesAmount / 1000 + totalClients) * 100) / 100;

      return {
        userId: t.id,
        firstName: t.firstName,
        lastName: t.lastName,
        rank: 0, // assigned below after sort
        score,
        totalTrainings,
        totalSalesAmount,
        totalClients,
        newClients: 0,
        conversionNewToAssigned: null,
        conversionAssignedToCompleted: null,
        conversionCompletedToRegular: null,
      };
    });

    // Sort by score descending and assign ranks
    entries.sort((a, b) => b.score - a.score);
    entries.forEach((e, i) => {
      e.rank = i + 1;
    });

    return entries;
  }

  private resolvePeriodDate(period?: string): Date | null {
    if (!period) return null;
    const now = new Date();
    switch (period) {
      case 'week': {
        const d = new Date(now);
        d.setDate(d.getDate() - 7);
        return d;
      }
      case 'month': {
        const d = new Date(now);
        d.setMonth(d.getMonth() - 1);
        return d;
      }
      case 'quarter': {
        const d = new Date(now);
        d.setMonth(d.getMonth() - 3);
        return d;
      }
      case 'year': {
        const d = new Date(now);
        d.setFullYear(d.getFullYear() - 1);
        return d;
      }
      default:
        return null;
    }
  }

  private buildTrainingWhere(
    clubId: string,
    trainerId?: string,
    dateFrom?: string,
    dateTo?: string,
  ): Prisma.TrainingWhereInput {
    return {
      clubId,
      ...(trainerId ? { trainerId } : {}),
      ...(dateFrom || dateTo
        ? {
            scheduledAt: {
              ...(dateFrom ? { gte: new Date(dateFrom) } : {}),
              ...(dateTo ? { lte: new Date(dateTo) } : {}),
            },
          }
        : {}),
    };
  }

  private buildSaleWhere(
    clubId: string,
    trainerId?: string,
    dateFrom?: string,
    dateTo?: string,
  ): Prisma.SaleWhereInput {
    return {
      clubId,
      ...(trainerId ? { trainerId } : {}),
      ...(dateFrom || dateTo
        ? {
            createdAt: {
              ...(dateFrom ? { gte: new Date(dateFrom) } : {}),
              ...(dateTo ? { lte: new Date(dateTo) } : {}),
            },
          }
        : {}),
    };
  }

  private buildClientWhere(
    clubId: string,
    trainerId?: string,
  ): Prisma.ClientWhereInput {
    return {
      clubId,
      ...(trainerId ? { assignedTrainerId: trainerId } : {}),
    };
  }

  private buildFunnelDateFilter(
    clubId: string,
    dateFrom?: string,
    dateTo?: string,
  ): Prisma.FunnelEntryWhereInput {
    return {
      clubId,
      ...(dateFrom || dateTo
        ? {
            createdAt: {
              ...(dateFrom ? { gte: new Date(dateFrom) } : {}),
              ...(dateTo ? { lte: new Date(dateTo) } : {}),
            },
          }
        : {}),
    };
  }
}

// ---------------------------------------------------------------------------
// Utility
// ---------------------------------------------------------------------------

/**
 * Safe division: returns 0 when denominator is 0,
 * otherwise percentage rounded to 2 decimal places.
 */
function safeDiv(numerator: number, denominator: number): number {
  return denominator === 0
    ? 0
    : Math.round((numerator / denominator) * 10000) / 100;
}
