import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { LoyaltyProgramType, UserRole } from '@prisma/client';
import { PrismaService } from '../../database/prisma.service';
import { CreateLoyaltyProgramDto } from './dto/create-loyalty-program.dto';
import { UpdateLoyaltyProgramDto } from './dto/update-loyalty-program.dto';

type RequestUser = {
  id: string;
  role: UserRole;
  shopId: string | null;
};

function assertCreatePayload(dto: CreateLoyaltyProgramDto) {
  if (dto.type === LoyaltyProgramType.PERCENTAGE) {
    if (dto.percentageRate == null) {
      throw new BadRequestException(
        'percentageRate is required for PERCENTAGE',
      );
    }
    return;
  }
  if (dto.type === LoyaltyProgramType.INCREMENTAL) {
    if (dto.stepAmount == null)
      throw new BadRequestException('stepAmount is required for INCREMENTAL');
    if (dto.pointsPerStep == null)
      throw new BadRequestException(
        'pointsPerStep is required for INCREMENTAL',
      );
    return;
  }
  throw new BadRequestException('Invalid loyalty program type');
}

function normalizeForType(payload: {
  type: LoyaltyProgramType;
  percentageRate?: number | null;
  stepAmount?: number | null;
  pointsPerStep?: number | null;
}) {
  if (payload.type === LoyaltyProgramType.PERCENTAGE) {
    if (payload.percentageRate == null) {
      throw new BadRequestException(
        'percentageRate is required for PERCENTAGE',
      );
    }
    return {
      type: payload.type,
      percentageRate: payload.percentageRate,
      stepAmount: null,
      pointsPerStep: null,
    };
  }
  if (payload.type === LoyaltyProgramType.INCREMENTAL) {
    if (payload.stepAmount == null)
      throw new BadRequestException('stepAmount is required for INCREMENTAL');
    if (payload.pointsPerStep == null)
      throw new BadRequestException(
        'pointsPerStep is required for INCREMENTAL',
      );
    return {
      type: payload.type,
      percentageRate: null,
      stepAmount: payload.stepAmount,
      pointsPerStep: payload.pointsPerStep,
    };
  }
  throw new BadRequestException('Invalid loyalty program type');
}

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

  private async assertCanAccessShopPrograms(user: RequestUser, shopId: string) {
    if (user.role === UserRole.SUPERADMIN) return;
    if (!user.shopId || user.shopId !== shopId)
      throw new ForbiddenException('Forbidden');

    if (user.role === UserRole.ADMIN) {
      const shop = await this.prisma.shop.findUnique({
        where: { id: shopId },
        select: { adminId: true },
      });
      if (!shop) throw new NotFoundException('Shop not found');
      if (shop.adminId !== user.id) throw new ForbiddenException('Forbidden');
    }
  }

  async list(user: RequestUser, shopIdQuery?: string) {
    if (user.role === UserRole.SUPERADMIN) {
      const where = shopIdQuery ? { shopId: shopIdQuery } : undefined;
      return this.prisma.loyaltyProgram.findMany({
        where,
        orderBy: { createdAt: 'desc' },
      });
    }

    if (!user.shopId) throw new ForbiddenException('No shop selected');
    await this.assertCanAccessShopPrograms(user, user.shopId);
    return this.prisma.loyaltyProgram.findMany({
      where: { shopId: user.shopId },
      orderBy: { createdAt: 'desc' },
    });
  }

  async create(user: RequestUser, dto: CreateLoyaltyProgramDto) {
    if (!user.shopId) throw new ForbiddenException('No shop selected');
    await this.assertCanAccessShopPrograms(user, user.shopId);
    assertCreatePayload(dto);

    const normalized = normalizeForType({
      type: dto.type,
      percentageRate: dto.percentageRate ?? null,
      stepAmount: dto.stepAmount ?? null,
      pointsPerStep: dto.pointsPerStep ?? null,
    });

    return this.prisma.loyaltyProgram.create({
      data: {
        shopId: user.shopId,
        name: dto.name,
        ...normalized,
        isActive: true,
      },
    });
  }

  async update(user: RequestUser, id: string, dto: UpdateLoyaltyProgramDto) {
    const existing = await this.prisma.loyaltyProgram.findUnique({
      where: { id },
      select: {
        id: true,
        shopId: true,
        name: true,
        type: true,
        isActive: true,
        percentageRate: true,
        stepAmount: true,
        pointsPerStep: true,
      },
    });
    if (!existing) throw new NotFoundException('Loyalty program not found');

    await this.assertCanAccessShopPrograms(user, existing.shopId);

    const nextType = dto.type ?? existing.type;
    const normalized = normalizeForType({
      type: nextType,
      percentageRate: dto.percentageRate ?? existing.percentageRate ?? null,
      stepAmount: dto.stepAmount ?? existing.stepAmount ?? null,
      pointsPerStep: dto.pointsPerStep ?? existing.pointsPerStep ?? null,
    });

    return this.prisma.loyaltyProgram.update({
      where: { id },
      data: {
        name: dto.name ?? existing.name,
        isActive: dto.isActive ?? existing.isActive,
        ...normalized,
      },
    });
  }

  async setActive(user: RequestUser, id: string, isActive: boolean) {
    const existing = await this.prisma.loyaltyProgram.findUnique({
      where: { id },
      select: { id: true, shopId: true },
    });
    if (!existing) throw new NotFoundException('Loyalty program not found');
    await this.assertCanAccessShopPrograms(user, existing.shopId);
    return this.prisma.loyaltyProgram.update({
      where: { id },
      data: { isActive },
    });
  }

  async remove(user: RequestUser, id: string) {
    const existing = await this.prisma.loyaltyProgram.findUnique({
      where: { id },
      select: { id: true, shopId: true },
    });
    if (!existing) throw new NotFoundException('Loyalty program not found');
    await this.assertCanAccessShopPrograms(user, existing.shopId);
    await this.prisma.loyaltyProgram.delete({ where: { id } });
    return { ok: true };
  }
}
