import { Injectable, NotFoundException } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { PrismaService } from '../../prisma/prisma.service';
import { CreateClientDto } from './dto/create-client.dto';
import { UpdateClientDto } from './dto/update-client.dto';
import { FindClientsDto } from './dto/find-clients.dto';
import { CreateGoalDto } from './dto/create-goal.dto';
import { CreateInjuryDto } from './dto/create-injury.dto';
import { Prisma } from '@prisma/client';

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

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

  /**
   * Find all clients with cursor-based pagination, search, and filters.
   * RLS ensures club isolation via TenantMiddleware.
   */
  async findAll(
    clubId: string,
    params: FindClientsDto,
  ): Promise<PaginatedResult<any>> {
    const {
      cursor,
      limit = 20,
      search,
      assignedTrainerId,
      sortBy = 'createdAt',
      sortOrder = 'desc',
    } = params;

    const where: Prisma.ClientWhereInput = {
      clubId,
      ...(assignedTrainerId ? { assignedTrainerId } : {}),
      ...(search ? this.buildSearchFilter(search) : {}),
    };

    const clients = await this.prisma.client.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { [sortBy]: sortOrder },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
      },
    });

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

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

  /**
   * Find a single client by ID with all relations.
   */
  async findById(clubId: string, id: string) {
    const client = await this.prisma.client.findFirst({
      where: { id, clubId },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
        goals: { orderBy: { createdAt: 'desc' } },
        injuries: { orderBy: { createdAt: 'desc' } },
        memberships: {
          orderBy: { endDate: 'desc' },
          take: 10,
        },
      },
    });

    if (!client) {
      throw new NotFoundException(`Client with id ${id} not found`);
    }

    return client;
  }

  /**
   * Create a new client within the club.
   */
  async create(clubId: string, dto: CreateClientDto) {
    const client = await this.prisma.client.create({
      data: {
        clubId,
        firstName: dto.firstName,
        lastName: dto.lastName,
        patronymic: dto.patronymic,
        phone: dto.phone,
        email: dto.email,
        birthDate: dto.birthDate ? new Date(dto.birthDate) : undefined,
        gender: dto.gender,
        notes: dto.notes,
        source: dto.source,
        assignedTrainerId: dto.assignedTrainerId,
      },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
      },
    });

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

    return client;
  }

  /**
   * Update an existing client.
   */
  async update(clubId: string, id: string, dto: UpdateClientDto) {
    await this.ensureClientExists(clubId, id);

    const client = await this.prisma.client.update({
      where: { id },
      data: {
        ...(dto.firstName !== undefined ? { firstName: dto.firstName } : {}),
        ...(dto.lastName !== undefined ? { lastName: dto.lastName } : {}),
        ...(dto.patronymic !== undefined
          ? { patronymic: dto.patronymic }
          : {}),
        ...(dto.phone !== undefined ? { phone: dto.phone } : {}),
        ...(dto.email !== undefined ? { email: dto.email } : {}),
        ...(dto.birthDate !== undefined
          ? { birthDate: new Date(dto.birthDate) }
          : {}),
        ...(dto.gender !== undefined ? { gender: dto.gender } : {}),
        ...(dto.notes !== undefined ? { notes: dto.notes } : {}),
        ...(dto.source !== undefined ? { source: dto.source } : {}),
        ...(dto.assignedTrainerId !== undefined
          ? { assignedTrainerId: dto.assignedTrainerId }
          : {}),
      },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
      },
    });

    this.eventEmitter.emit('client.updated', { clubId, data: client });

    return client;
  }

  /**
   * Delete a client (hard delete).
   */
  async delete(clubId: string, id: string): Promise<void> {
    await this.ensureClientExists(clubId, id);
    await this.prisma.client.delete({ where: { id } });
  }

  /**
   * Find sleeping clients: active membership + lastActivityAt > 30 days ago.
   * Optionally filter by assignedTrainerId (for TRAINER role).
   */
  async findSleeping(
    clubId: string,
    params: FindClientsDto & { trainerFilter?: string },
  ): Promise<PaginatedResult<any>> {
    const { cursor, limit = 20, trainerFilter } = params;

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

    const where: Prisma.ClientWhereInput = {
      clubId,
      lastActivityAt: { lt: thirtyDaysAgo },
      memberships: {
        some: { isActive: true, endDate: { gte: new Date() } },
      },
      ...(trainerFilter ? { assignedTrainerId: trainerFilter } : {}),
    };

    const clients = await this.prisma.client.findMany({
      where,
      take: limit + 1,
      ...(cursor ? { cursor: { id: cursor }, skip: 1 } : {}),
      orderBy: { lastActivityAt: 'asc' },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
        memberships: {
          where: { isActive: true, endDate: { gte: new Date() } },
          take: 1,
        },
      },
    });

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

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

  /**
   * Assign a trainer to a client.
   */
  async assignTrainer(clubId: string, clientId: string, trainerId: string) {
    await this.ensureClientExists(clubId, clientId);

    return this.prisma.client.update({
      where: { id: clientId },
      data: { assignedTrainerId: trainerId },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
      },
    });
  }

  /**
   * Full-text search clients by name, phone, or email.
   */
  async search(clubId: string, query: string) {
    return this.prisma.client.findMany({
      where: {
        clubId,
        ...this.buildSearchFilter(query),
      },
      take: 50,
      orderBy: { lastName: 'asc' },
      include: {
        assignedTrainer: {
          select: { id: true, firstName: true, lastName: true },
        },
      },
    });
  }

  /**
   * List goals for a specific client.
   */
  async findGoals(clubId: string, clientId: string) {
    await this.ensureClientExists(clubId, clientId);

    return this.prisma.clientGoal.findMany({
      where: { clubId, clientId },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * Add a goal to a client.
   */
  async createGoal(clubId: string, clientId: string, dto: CreateGoalDto) {
    await this.ensureClientExists(clubId, clientId);

    return this.prisma.clientGoal.create({
      data: {
        clubId,
        clientId,
        title: dto.title,
      },
    });
  }

  /**
   * List injuries for a specific client.
   */
  async findInjuries(clubId: string, clientId: string) {
    await this.ensureClientExists(clubId, clientId);

    return this.prisma.clientInjury.findMany({
      where: { clubId, clientId },
      orderBy: { createdAt: 'desc' },
    });
  }

  /**
   * Add an injury to a client.
   */
  async createInjury(
    clubId: string,
    clientId: string,
    dto: CreateInjuryDto,
  ) {
    await this.ensureClientExists(clubId, clientId);

    return this.prisma.clientInjury.create({
      data: {
        clubId,
        clientId,
        description: dto.description,
        severity: dto.severity,
      },
    });
  }

  /**
   * Build an OR filter for searching by firstName, lastName, phone, or email.
   */
  private buildSearchFilter(search: string): Prisma.ClientWhereInput {
    return {
      OR: [
        { firstName: { contains: search, mode: 'insensitive' } },
        { lastName: { contains: search, mode: 'insensitive' } },
        { phone: { contains: search } },
        { email: { contains: search, mode: 'insensitive' } },
      ],
    };
  }

  /**
   * Ensure a client exists within the given club, or throw NotFoundException.
   */
  private async ensureClientExists(
    clubId: string,
    id: string,
  ): Promise<void> {
    const count = await this.prisma.client.count({
      where: { id, clubId },
    });

    if (count === 0) {
      throw new NotFoundException(`Client with id ${id} not found`);
    }
  }
}
