import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { CouponType, Prisma, UserRole } from '@prisma/client';
import { PrismaService } from '../database/prisma.service';
import { UpsertCategoryDto } from './dto/upsert-category.dto';
import { UpsertProductDto } from './dto/upsert-product.dto';

type RequestUser = {
  sub: string;
  role: UserRole;
};

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

  async listCategories(user: RequestUser) {
    const restaurantId = await this.resolveRestaurantId(user);
    return this.prisma.productCategory.findMany({
      where: restaurantId ? { restaurantId } : undefined,
      include: { _count: { select: { products: true } } },
      orderBy: [{ sortOrder: 'asc' }, { name: 'asc' }],
    });
  }

  async createCategory(user: RequestUser, body: UpsertCategoryDto) {
    return this.prisma.productCategory.create({
      data: {
        restaurantId: await this.requireRestaurantId(user),
        name: body.name,
        sortOrder: body.sortOrder ?? 0,
      },
      include: { _count: { select: { products: true } } },
    });
  }

  async updateCategory(user: RequestUser, id: string, body: Partial<UpsertCategoryDto>) {
    await this.findOwnedCategory(user, id);
    return this.prisma.productCategory.update({
      where: { id },
      data: {
        name: body.name,
        sortOrder: body.sortOrder,
      },
      include: { _count: { select: { products: true } } },
    });
  }

  async archiveCategory(user: RequestUser, id: string) {
    const category = await this.findOwnedCategory(user, id);
    await this.prisma.product.updateMany({
      where: { categoryId: category.id },
      data: { categoryId: null, isActive: false },
    });
    await this.prisma.productCategory.delete({ where: { id: category.id } });
    return { ok: true, id: category.id };
  }

  async listProducts(user: RequestUser) {
    const restaurantId = await this.resolveRestaurantId(user);
    return this.prisma.product.findMany({
      where: restaurantId ? { restaurantId } : undefined,
      include: { category: true },
      orderBy: [{ isActive: 'desc' }, { createdAt: 'desc' }],
    });
  }

  async createProduct(user: RequestUser, body: UpsertProductDto) {
    const restaurantId = await this.requireRestaurantId(user);
    if (body.categoryId) {
      await this.findOwnedCategory(user, body.categoryId);
    }

    return this.prisma.product.create({
      data: {
        restaurantId,
        categoryId: body.categoryId || null,
        name: body.name,
        description: body.description,
        imageUrl: body.imageUrl,
        price: new Prisma.Decimal(body.price),
        isActive: body.isActive ?? true,
      },
      include: { category: true },
    });
  }

  async updateProduct(user: RequestUser, id: string, body: Partial<UpsertProductDto>) {
    await this.findOwnedProduct(user, id);
    if (body.categoryId) {
      await this.findOwnedCategory(user, body.categoryId);
    }

    return this.prisma.product.update({
      where: { id },
      data: {
        categoryId: body.categoryId === undefined ? undefined : body.categoryId || null,
        name: body.name,
        description: body.description,
        imageUrl: body.imageUrl,
        price: body.price === undefined ? undefined : new Prisma.Decimal(body.price),
        isActive: body.isActive,
      },
      include: { category: true },
    });
  }

  async archiveProduct(user: RequestUser, id: string) {
    await this.findOwnedProduct(user, id);
    return this.prisma.product.update({
      where: { id },
      data: { isActive: false },
      include: { category: true },
    });
  }

  async getPublicStorefront(slug: string) {
    const now = new Date();
    const restaurant = await this.prisma.restaurant.findUnique({
      where: { slug },
      include: {
        productCategories: {
          orderBy: [{ sortOrder: 'asc' }, { name: 'asc' }],
          include: {
            products: {
              where: { isActive: true },
              orderBy: { name: 'asc' },
              include: {
                additionals: {
                  where: { isActive: true },
                  orderBy: { name: 'asc' },
                },
                variations: {
                  orderBy: { name: 'asc' },
                },
              },
            },
          },
        },
        promotions: {
          where: {
            isActive: true,
            startsAt: { lte: now },
            endsAt: { gte: now },
          },
          orderBy: [{ discountValue: 'desc' }, { endsAt: 'asc' }],
        },
      },
    });

    if (!restaurant) {
      throw new NotFoundException('Loja publica nao encontrada.');
    }

    const coupons = await this.prisma.coupon.findMany({
      where: {
        restaurantId: restaurant.id,
        isActive: true,
        validFrom: { lte: now },
        validUntil: { gte: now },
      },
      orderBy: [{ value: 'desc' }, { validUntil: 'asc' }],
      take: 12,
    });

    return {
      ...restaurant,
      activeCoupons: coupons.map((coupon) => ({
        id: coupon.id,
        code: coupon.code,
        type: coupon.type,
        value: Number(coupon.value),
        minOrderValue: coupon.minOrderValue ? Number(coupon.minOrderValue) : null,
        validUntil: coupon.validUntil,
      })),
      activePromotions: restaurant.promotions.map((promotion) => ({
        id: promotion.id,
        title: promotion.title,
        description: promotion.description,
        discountType: promotion.discountType,
        discountValue: Number(promotion.discountValue),
        endsAt: promotion.endsAt,
      })),
    };
  }

  async listPublicStores(city?: string, search?: string, category?: string) {
    const normalizedSearch = search?.trim();
    const normalizedCategory = category?.trim();

    const stores = await this.prisma.restaurant.findMany({
      where: {
        status: 'ACTIVE',
        isPaused: false,
        isOpen: true,
        city: city || undefined,
        ...(normalizedSearch || normalizedCategory
          ? {
              AND: [
                normalizedSearch
                  ? {
                      OR: [
                        { name: { contains: normalizedSearch, mode: 'insensitive' } },
                        { description: { contains: normalizedSearch, mode: 'insensitive' } },
                        {
                          products: {
                            some: {
                              isActive: true,
                              OR: [
                                { name: { contains: normalizedSearch, mode: 'insensitive' } },
                                { description: { contains: normalizedSearch, mode: 'insensitive' } },
                              ],
                            },
                          },
                        },
                        {
                          productCategories: {
                            some: { name: { contains: normalizedSearch, mode: 'insensitive' } },
                          },
                        },
                      ],
                    }
                  : {},
                normalizedCategory
                  ? {
                      OR: [
                        {
                          productCategories: {
                            some: { name: { contains: normalizedCategory, mode: 'insensitive' } },
                          },
                        },
                        {
                          products: {
                            some: {
                              isActive: true,
                              category: {
                                name: { contains: normalizedCategory, mode: 'insensitive' },
                              },
                            },
                          },
                        },
                      ],
                    }
                  : {},
              ],
            }
          : {}),
      },
      orderBy: [{ city: 'asc' }, { name: 'asc' }],
      select: {
        id: true,
        slug: true,
        name: true,
        description: true,
        street: true,
        number: true,
        zipCode: true,
        city: true,
        neighborhood: true,
        logoUrl: true,
        coverImageUrl: true,
        acceptsCashOnDelivery: true,
        baseDeliveryFee: true,
        pricePerKm: true,
        productCategories: {
          orderBy: [{ sortOrder: 'asc' }, { name: 'asc' }],
          select: {
            name: true,
          },
        },
        products: {
          where: {
            isActive: true,
            ...(normalizedSearch
              ? {
                  OR: [
                    { name: { contains: normalizedSearch, mode: 'insensitive' } },
                    { description: { contains: normalizedSearch, mode: 'insensitive' } },
                  ],
                }
              : normalizedCategory
                ? {
                    category: {
                      name: { contains: normalizedCategory, mode: 'insensitive' },
                    },
                  }
                : {}),
          },
          orderBy: [{ name: 'asc' }],
          select: {
            name: true,
            imageUrl: true,
            description: true,
          },
          take: 4,
        },
      },
      take: 100,
    });

    return stores.map((store) => ({
      ...store,
      categories: store.productCategories.map((entry) => entry.name),
      featuredProducts: store.products.map((entry) => ({
        name: entry.name,
        imageUrl: entry.imageUrl,
        description: entry.description,
      })),
    }));
  }

  applyDiscountRule(
    baseAmount: number,
    discountType: CouponType,
    value: number,
    deliveryFee = 0,
  ) {
    if (discountType === CouponType.FREE_DELIVERY) {
      return Number(Math.min(baseAmount + deliveryFee, deliveryFee).toFixed(2));
    }

    if (discountType === CouponType.PERCENTAGE) {
      return Number(Math.min(baseAmount, (baseAmount * value) / 100).toFixed(2));
    }

    return Number(Math.min(baseAmount, value).toFixed(2));
  }

  private async resolveRestaurantId(user: RequestUser) {
    if (user.role === UserRole.ADMIN || user.role === UserRole.SUPER_ADMIN) {
      return null;
    }
    if (user.role !== UserRole.RESTAURANT) {
      throw new ForbiddenException('Acesso ao catalogo restrito.');
    }

    const restaurant = await this.prisma.restaurant.findUnique({
      where: { userId: user.sub },
      select: { id: true },
    });
    if (!restaurant) {
      throw new NotFoundException('Restaurante autenticado nao encontrado.');
    }

    return restaurant.id;
  }

  private async requireRestaurantId(user: RequestUser) {
    const restaurantId = await this.resolveRestaurantId(user);
    if (!restaurantId) {
      throw new ForbiddenException('Somente parceiros podem alterar o catalogo.');
    }
    return restaurantId;
  }

  private async findOwnedCategory(user: RequestUser, id: string) {
    const restaurantId = await this.requireRestaurantId(user);
    const category = await this.prisma.productCategory.findUnique({ where: { id } });
    if (!category || category.restaurantId !== restaurantId) {
      throw new NotFoundException('Categoria nao encontrada neste parceiro.');
    }
    return category;
  }

  private async findOwnedProduct(user: RequestUser, id: string) {
    const restaurantId = await this.requireRestaurantId(user);
    const product = await this.prisma.product.findUnique({ where: { id } });
    if (!product || product.restaurantId !== restaurantId) {
      throw new NotFoundException('Produto nao encontrado neste parceiro.');
    }
    return product;
  }
}
