import {
  BadRequestException,
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { LoyaltyEarnType, UserRole } from '@prisma/client';
import { PrismaService } from '../../database/prisma.service';
import { UpsertLoyaltySettingsDto } from './dto/upsert-loyalty-settings.dto';

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

function normalizeEarnFields(dto: UpsertLoyaltySettingsDto) {
  if (dto.earnType === LoyaltyEarnType.PERCENTAGE) {
    if (dto.percentageRate == null)
      throw new BadRequestException(
        'percentageRate is required for PERCENTAGE',
      );
    return {
      earnType: dto.earnType,
      percentageRate: dto.percentageRate,
      stepAmount: null,
      pointsPerStep: null,
    };
  }
  if (dto.earnType === LoyaltyEarnType.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 {
      earnType: dto.earnType,
      percentageRate: null,
      stepAmount: dto.stepAmount,
      pointsPerStep: dto.pointsPerStep,
    };
  }
  throw new BadRequestException('Invalid earnType');
}

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

  private async assertCanAccessShop(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');
    }
  }

  private defaultsForShop(shopId: string) {
    return {
      shopId,
      minOrderFirstPoint: 200,
      minOrderApplyPoint: 200,
      validityMonths: 6,
      maxEarnablePoints: 100,
      maxRedeemPercent: 15,
      earnType: LoyaltyEarnType.INCREMENTAL,
      percentageRate: null,
      stepAmount: 50,
      pointsPerStep: 10,
      welcomeBonus: 10,
      referralBonus: 10,
    };
  }

  async getForCurrentShop(user: RequestUser) {
    if (!user.shopId) throw new ForbiddenException('No shop selected');
    await this.assertCanAccessShop(user, user.shopId);

    const existing = await this.prisma.loyaltySettings.findUnique({
      where: { shopId: user.shopId },
    });
    return existing ?? this.defaultsForShop(user.shopId);
  }

  async getByShopId(user: RequestUser, shopId: string) {
    await this.assertCanAccessShop(user, shopId);
    const existing = await this.prisma.loyaltySettings.findUnique({
      where: { shopId },
    });
    return existing ?? this.defaultsForShop(shopId);
  }

  async upsertForCurrentShop(user: RequestUser, dto: UpsertLoyaltySettingsDto) {
    if (!user.shopId) throw new ForbiddenException('No shop selected');
    await this.assertCanAccessShop(user, user.shopId);

    const normalized = normalizeEarnFields(dto);

    return this.prisma.loyaltySettings.upsert({
      where: { shopId: user.shopId },
      create: {
        shopId: user.shopId,
        minOrderFirstPoint: dto.minOrderFirstPoint,
        minOrderApplyPoint: dto.minOrderApplyPoint,
        validityMonths: dto.validityMonths,
        maxEarnablePoints: dto.maxEarnablePoints,
        maxRedeemPercent: dto.maxRedeemPercent,
        ...normalized,
        welcomeBonus: dto.welcomeBonus,
        referralBonus: dto.referralBonus,
      },
      update: {
        minOrderFirstPoint: dto.minOrderFirstPoint,
        minOrderApplyPoint: dto.minOrderApplyPoint,
        validityMonths: dto.validityMonths,
        maxEarnablePoints: dto.maxEarnablePoints,
        maxRedeemPercent: dto.maxRedeemPercent,
        ...normalized,
        welcomeBonus: dto.welcomeBonus,
        referralBonus: dto.referralBonus,
      },
    });
  }

  async upsertByShopId(
    user: RequestUser,
    shopId: string,
    dto: UpsertLoyaltySettingsDto,
  ) {
    await this.assertCanAccessShop(user, shopId);
    const normalized = normalizeEarnFields(dto);
    return this.prisma.loyaltySettings.upsert({
      where: { shopId },
      create: {
        shopId,
        minOrderFirstPoint: dto.minOrderFirstPoint,
        minOrderApplyPoint: dto.minOrderApplyPoint,
        validityMonths: dto.validityMonths,
        maxEarnablePoints: dto.maxEarnablePoints,
        maxRedeemPercent: dto.maxRedeemPercent,
        ...normalized,
        welcomeBonus: dto.welcomeBonus,
        referralBonus: dto.referralBonus,
      },
      update: {
        minOrderFirstPoint: dto.minOrderFirstPoint,
        minOrderApplyPoint: dto.minOrderApplyPoint,
        validityMonths: dto.validityMonths,
        maxEarnablePoints: dto.maxEarnablePoints,
        maxRedeemPercent: dto.maxRedeemPercent,
        ...normalized,
        welcomeBonus: dto.welcomeBonus,
        referralBonus: dto.referralBonus,
      },
    });
  }
}
