import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../../../prisma/prisma.service';
import { BookingEventsService } from '../core/booking-events.service';
import {
  IssueMembershipDto,
  FreezeMembershipDto,
  FindClientMembershipsQuery,
} from '../dto/membership.dto';
import { Decimal } from '@prisma/client/runtime/library';

const MEMBERSHIP_INCLUDE = {
  type: { select: { id: true, name: true, kind: true, serviceIds: true } },
  client: { select: { id: true, firstName: true, lastName: true, phone: true } },
};

@Injectable()
export class ClientMembershipsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly events: BookingEventsService,
  ) {}

  // ─── List ──────────────────────────────────────────────

  async findAll(clubId: string, query: FindClientMembershipsQuery) {
    const limit = Math.min(query.limit ?? 50, 100);

    const where: Record<string, any> = { clubId };
    if (query.clientId) where.clientId = query.clientId;
    if (query.activeOnly) where.isActive = true;

    const items = await this.prisma.bookingClientMembership.findMany({
      where,
      include: MEMBERSHIP_INCLUDE,
      take: limit + 1,
      ...(query.cursor ? { cursor: { id: query.cursor }, skip: 1 } : {}),
      orderBy: { createdAt: 'desc' },
    });

    const hasMore = items.length > limit;
    if (hasMore) items.pop();

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

  // ─── Get by ID ─────────────────────────────────────────

  async findById(id: string) {
    const membership = await this.prisma.bookingClientMembership.findUnique({
      where: { id },
      include: MEMBERSHIP_INCLUDE,
    });
    if (!membership) throw new NotFoundException(`Membership ${id} not found`);
    return membership;
  }

  // ─── Issue membership to client ────────────────────────

  async issue(clubId: string, dto: IssueMembershipDto, userId?: string) {
    const type = await this.prisma.bookingMembershipType.findUniqueOrThrow({
      where: { id: dto.typeId },
    });

    if (!type.isActive) {
      throw new BadRequestException('Membership type is inactive');
    }

    const shouldActivate = dto.activateNow ?? type.autoActivate;
    const now = new Date();

    const membership = await this.prisma.bookingClientMembership.create({
      data: {
        clubId,
        clientId: dto.clientId,
        typeId: dto.typeId,
        remainingVisits: type.totalVisits,
        remainingHours: type.totalHours,
        activatedAt: shouldActivate ? now : null,
        expiresAt: shouldActivate
          ? new Date(now.getTime() + type.validityDays * 24 * 60 * 60 * 1000)
          : null,
        isActive: true,
      },
      include: MEMBERSHIP_INCLUDE,
    });

    await this.events.emit('booking.membership.activated', {
      clubId,
      data: { membership, type },
      userId,
    });

    return membership;
  }

  // ─── Deduct visit ──────────────────────────────────────

  async deductVisit(membershipId: string) {
    const membership = await this.findById(membershipId);

    this.assertUsable(membership);

    if (membership.type.kind === 'UNLIMITED') return membership;

    const update: Record<string, any> = {};

    if (
      membership.remainingVisits !== null &&
      ['VISIT_BASED', 'MIXED'].includes(membership.type.kind)
    ) {
      if (membership.remainingVisits <= 0) {
        throw new BadRequestException('No remaining visits');
      }
      update.remainingVisits = { decrement: 1 };
    }

    // Auto-activate on first use
    if (!membership.activatedAt) {
      const now = new Date();
      const type = await this.prisma.bookingMembershipType.findUniqueOrThrow({
        where: { id: membership.type.id },
      });
      update.activatedAt = now;
      update.expiresAt = new Date(now.getTime() + type.validityDays * 24 * 60 * 60 * 1000);
    }

    const updated = await this.prisma.bookingClientMembership.update({
      where: { id: membershipId },
      data: update,
      include: MEMBERSHIP_INCLUDE,
    });

    // Check if depleted
    if (updated.remainingVisits !== null && updated.remainingVisits <= 0) {
      await this.prisma.bookingClientMembership.update({
        where: { id: membershipId },
        data: { isActive: false },
      });

      await this.events.emit('booking.membership.depleted', {
        clubId: updated.clubId,
        data: { membership: updated },
      });
    }

    return updated;
  }

  // ─── Deduct hours ──────────────────────────────────────

  async deductHours(membershipId: string, hours: number) {
    const membership = await this.findById(membershipId);

    this.assertUsable(membership);

    if (membership.type.kind === 'UNLIMITED') return membership;

    if (
      membership.remainingHours !== null &&
      ['HOUR_BASED', 'MIXED'].includes(membership.type.kind)
    ) {
      const remaining = Number(membership.remainingHours);
      if (remaining < hours) {
        throw new BadRequestException(
          `Not enough hours: ${remaining}h remaining, ${hours}h requested`,
        );
      }
    }

    const update: Record<string, any> = {};
    if (membership.remainingHours !== null) {
      update.remainingHours = new Decimal(Number(membership.remainingHours) - hours);
    }

    // Auto-activate
    if (!membership.activatedAt) {
      const now = new Date();
      const type = await this.prisma.bookingMembershipType.findUniqueOrThrow({
        where: { id: membership.type.id },
      });
      update.activatedAt = now;
      update.expiresAt = new Date(now.getTime() + type.validityDays * 24 * 60 * 60 * 1000);
    }

    const updated = await this.prisma.bookingClientMembership.update({
      where: { id: membershipId },
      data: update,
      include: MEMBERSHIP_INCLUDE,
    });

    if (updated.remainingHours !== null && Number(updated.remainingHours) <= 0) {
      await this.prisma.bookingClientMembership.update({
        where: { id: membershipId },
        data: { isActive: false },
      });

      await this.events.emit('booking.membership.depleted', {
        clubId: updated.clubId,
        data: { membership: updated },
      });
    }

    return updated;
  }

  // ─── Freeze ────────────────────────────────────────────

  async freeze(id: string, dto: FreezeMembershipDto) {
    const membership = await this.findById(id);

    if (!membership.isActive) {
      throw new BadRequestException('Membership is not active');
    }

    if (membership.frozenUntil && membership.frozenUntil > new Date()) {
      throw new BadRequestException('Membership is already frozen');
    }

    const frozenUntil = new Date(Date.now() + dto.days * 24 * 60 * 60 * 1000);

    // Extend expiry by freeze duration
    const newExpiry = membership.expiresAt
      ? new Date(membership.expiresAt.getTime() + dto.days * 24 * 60 * 60 * 1000)
      : null;

    return this.prisma.bookingClientMembership.update({
      where: { id },
      data: {
        frozenUntil,
        ...(newExpiry ? { expiresAt: newExpiry } : {}),
      },
      include: MEMBERSHIP_INCLUDE,
    });
  }

  // ─── Unfreeze ──────────────────────────────────────────

  async unfreeze(id: string) {
    const membership = await this.findById(id);

    if (!membership.frozenUntil || membership.frozenUntil <= new Date()) {
      throw new BadRequestException('Membership is not frozen');
    }

    // Recalculate expiry: subtract unused freeze days
    const unusedFreezeDays = Math.ceil(
      (membership.frozenUntil.getTime() - Date.now()) / (24 * 60 * 60 * 1000),
    );

    const newExpiry = membership.expiresAt
      ? new Date(membership.expiresAt.getTime() - unusedFreezeDays * 24 * 60 * 60 * 1000)
      : null;

    return this.prisma.bookingClientMembership.update({
      where: { id },
      data: {
        frozenUntil: null,
        ...(newExpiry ? { expiresAt: newExpiry } : {}),
      },
      include: MEMBERSHIP_INCLUDE,
    });
  }

  // ─── Check if membership is valid for a service ────────

  async isValidForService(membershipId: string, serviceId: string): Promise<boolean> {
    const membership = await this.prisma.bookingClientMembership.findUnique({
      where: { id: membershipId },
      include: { type: true },
    });

    if (!membership) return false;
    if (!this.isUsable(membership)) return false;

    // Empty serviceIds = covers all services
    if (membership.type.serviceIds.length === 0) return true;

    return membership.type.serviceIds.includes(serviceId);
  }

  // ─── Find valid memberships for client + service ───────

  async findValidForClient(clubId: string, clientId: string, serviceId: string) {
    const memberships = await this.prisma.bookingClientMembership.findMany({
      where: { clubId, clientId, isActive: true },
      include: { type: true },
    });

    return memberships.filter((m) => {
      if (!this.isUsable(m)) return false;
      if (m.type.serviceIds.length > 0 && !m.type.serviceIds.includes(serviceId)) {
        return false;
      }
      return true;
    });
  }

  // ─── Helpers ───────────────────────────────────────────

  private isUsable(membership: {
    isActive: boolean;
    frozenUntil?: Date | null;
    expiresAt?: Date | null;
    remainingVisits?: number | null;
    remainingHours?: any;
    type?: { kind: string } | null;
  }): boolean {
    if (!membership.isActive) return false;
    if (membership.frozenUntil && membership.frozenUntil > new Date()) return false;
    if (membership.expiresAt && membership.expiresAt < new Date()) return false;

    // Check remaining visits/hours
    if (membership.type?.kind === 'VISIT_BASED' && membership.remainingVisits != null) {
      if (membership.remainingVisits <= 0) return false;
    }
    if (membership.type?.kind === 'HOUR_BASED' && membership.remainingHours !== null) {
      if (Number(membership.remainingHours) <= 0) return false;
    }

    return true;
  }

  private assertUsable(membership: {
    isActive: boolean;
    frozenUntil?: Date | null;
    expiresAt?: Date | null;
  }) {
    if (!membership.isActive) {
      throw new BadRequestException('Membership is not active');
    }
    if (membership.frozenUntil && membership.frozenUntil > new Date()) {
      throw new BadRequestException('Membership is frozen');
    }
    if (membership.expiresAt && membership.expiresAt < new Date()) {
      throw new BadRequestException('Membership has expired');
    }
  }
}
