import { Injectable, NotFoundException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { normalizeName } from '../common/utils/normalize-name';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
import { UpsertNutritionDto } from './dto/upsert-nutrition.dto';
import { AiService } from '../ai/ai.service';
import { CategoriesService } from '../categories/categories.service';

@Injectable()
export class ProductsService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly aiService: AiService,
    private readonly categoriesService: CategoriesService,
  ) {}

  async findAll(filters?: { tag?: string; subcategory?: string }) {
    return this.prisma.product.findMany({
      where: {
        isActive: true,
        isPrivate: false,
        ...(filters?.subcategory ? { subcategory: filters.subcategory } : {}),
        ...(filters?.tag
          ? { tags: { some: { tag: { name: filters.tag } } } }
          : {}),
      },
      include: {
        tags: { include: { tag: true } },
        nutrition: true,
        categoryRef: { include: { parent: { include: { parent: true } } } },
      },
      orderBy: { name: 'asc' },
    });
  }

  async findByOwner(userId: number) {
    return this.prisma.product.findMany({
      where: { ownerId: userId, isPrivate: true, isActive: true },
      select: { id: true, name: true, canonicalName: true, categoryId: true },
      orderBy: { name: 'asc' },
    });
  }

  async createPrivate(data: CreateProductDto, userId: number) {
    const name = data.name.trim();
    // Privata produkters normalizedName är prefixade för att undvika kollision
    const normalizedName = `private:${userId}:${normalizeName(name)}`;

    const existing = await this.prisma.product.findUnique({
      where: { normalizedName },
    });

    if (existing && existing.isActive) return existing;

    if (existing) {
      return this.prisma.product.update({
        where: { id: existing.id },
        data: { isActive: true, deletedAt: null, name, canonicalName: name },
      });
    }

    return this.prisma.product.create({
      data: {
        name,
        normalizedName,
        canonicalName: name,
        isActive: true,
        isPrivate: true,
        ownerId: userId,
        ...(data.categoryId != null ? { categoryId: data.categoryId } : {}),
      },
    });
  }

  async findDuplicateCandidates() {
    const products = await this.prisma.product.findMany({
      where: {
        isActive: true,
      },
      orderBy: {
        name: 'asc',
      },
    });

    const grouped = new Map<string, typeof products>();

    for (const product of products) {
      const key = product.normalizedName;

      if (!grouped.has(key)) {
        grouped.set(key, []);
      }

      grouped.get(key)!.push(product);
    }

    return Array.from(grouped.entries())
      .filter(([, items]) => items.length > 1)
      .map(([normalizedName, items]) => ({
        normalizedName,
        count: items.length,
        products: items,
      }));
  }

  async findOne(id: number) {
    const product = await this.prisma.product.findUnique({
      where: { id },
    });

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

    return product;
  }

  async create(data: CreateProductDto, ownerId?: number) {
    const name = data.name.trim();
    const normalizedName = normalizeName(name);

    const existing = await this.prisma.product.findUnique({
      where: { normalizedName },
    });

    if (existing) {
      if (!existing.isActive) {
        return this.prisma.product.update({
          where: { id: existing.id },
          data: {
            isActive: true,
            deletedAt: null,
            name,
            canonicalName: name,
          },
        });
      }

      return existing;
    }

    if (!ownerId) {
      throw new Error('ownerId är obligatorisk för att skapa en produkt');
    }

    return this.prisma.product.create({
      data: {
        ownerId,
        name,
        normalizedName,
        canonicalName: name,
        isActive: true,
        deletedAt: null,
        ...(data.categoryId != null ? { categoryId: data.categoryId } : {}),
      },
    });
  }

  async update(id: number, data: UpdateProductDto) {
    await this.findOne(id);

    const updateData: {
      name?: string;
      normalizedName?: string;
      canonicalName?: string;
      category?: string | null;
      subcategory?: string | null;
      brand?: string | null;
      categoryId?: number | null;
    } = {};

    if (typeof data.name === 'string') {
      const name = data.name.trim();
      const normalizedName = normalizeName(name);

      const existing = await this.prisma.product.findUnique({
        where: { normalizedName },
      });

      if (existing && existing.id !== id) {
        // Om en annan produkt har samma namn, returnera ett tydligt fel
        throw new Error('Det finns redan en annan produkt med detta namn. Välj ett unikt namn.');
      }

      updateData.name = name;
      updateData.normalizedName = normalizedName;
    }

    if (typeof data.canonicalName === 'string') {
      updateData.canonicalName = data.canonicalName.trim() || undefined;
    }

    if (typeof data.category === 'string') {
      updateData.category = data.category.trim() || null;
    }

    if (typeof data.subcategory === 'string') {
      updateData.subcategory = data.subcategory.trim() || null;
    }

    if (typeof data.brand === 'string') {
      updateData.brand = data.brand.trim() || null;
    }

    if ('categoryId' in data) {
      updateData.categoryId = data.categoryId ?? null;
    }

    return this.prisma.product.update({
      where: { id },
      data: updateData,
      include: { tags: { include: { tag: true } }, nutrition: true },
    });
  }

  async updateCanonicalName(id: number, canonicalName: string) {
    await this.findOne(id);

    const cleaned = canonicalName.trim();

    return this.prisma.product.update({
      where: { id },
      data: {
        canonicalName: cleaned,
      },
    });
  }

  async findDeleted() {
    return this.prisma.product.findMany({
      where: { isActive: false },
      orderBy: { deletedAt: 'desc' },
    });
  }

  async remove(id: number) {
    await this.findOne(id);

    return this.prisma.product.update({
      where: { id },
      data: {
        isActive: false,
        deletedAt: new Date(),
      },
    });
  }

  async permanentDelete(id: number) {
    const product = await this.prisma.product.findUnique({ where: { id } });
    if (!product) {
      throw new NotFoundException(`Product with id ${id} not found`);
    }
    // Ta bort beroenden först
    await this.prisma.productTag.deleteMany({ where: { productId: id } });
    await this.prisma.userProduct.deleteMany({ where: { productId: id } });
    return this.prisma.product.delete({ where: { id } });
  }

  async restore(id: number) {
    const product = await this.findOne(id);

    if (product.isActive) {
      return product;
    }

    return this.prisma.product.update({
      where: { id },
      data: {
        isActive: true,
        deletedAt: null,
      },
    });
  }

  async merge(sourceProductId: number, targetProductId: number) {
    if (sourceProductId === targetProductId) {
      throw new Error('sourceProductId och targetProductId kan inte vara samma');
    }

    const source = await this.prisma.product.findUnique({
      where: { id: sourceProductId },
    });

    if (!source) {
      throw new NotFoundException(
        `Source product with id ${sourceProductId} not found`,
      );
    }

    const target = await this.prisma.product.findUnique({
      where: { id: targetProductId },
    });

    if (!target) {
      throw new NotFoundException(
        `Target product with id ${targetProductId} not found`,
      );
    }

    return this.prisma.$transaction(async (tx) => {
      const movedInventoryCount = await tx.inventoryItem.updateMany({
        where: { productId: sourceProductId },
        data: { productId: targetProductId },
      });

      const softDeletedSource = await tx.product.update({
        where: { id: sourceProductId },
        data: {
          isActive: false,
          deletedAt: new Date(),
        },
      });

      return {
        message: 'Products merged successfully',
        sourceProductId,
        targetProductId,
        movedInventoryCount: movedInventoryCount.count,
        softDeletedSource,
      };
    });
  }

  async previewMerge(sourceProductId: number, targetProductId: number) {
    if (sourceProductId === targetProductId) {
      throw new Error('sourceProductId och targetProductId kan inte vara samma');
    }

    const [source, target, sourceInventoryCount, targetInventoryCount] =
      await Promise.all([
        this.prisma.product.findUnique({
          where: { id: sourceProductId },
        }),
        this.prisma.product.findUnique({
          where: { id: targetProductId },
        }),
        this.prisma.inventoryItem.count({
          where: { productId: sourceProductId },
        }),
        this.prisma.inventoryItem.count({
          where: { productId: targetProductId },
        }),
      ]);

    if (!source) {
      throw new NotFoundException(
        `Source product with id ${sourceProductId} not found`,
      );
    }

    if (!target) {
      throw new NotFoundException(
        `Target product with id ${targetProductId} not found`,
      );
    }

    return {
      source: {
        ...source,
        inventoryCount: sourceInventoryCount,
      },
      target: {
        ...target,
        inventoryCount: targetInventoryCount,
      },
      outcome: {
        inventoryItemsToMove: sourceInventoryCount,
        sourceWillBeSoftDeleted: true,
        targetWillRemainActive: true,
      },
    };
  }

  async backfillCanonicalNames() {
    const products = await this.prisma.product.findMany({
      where: {
        canonicalName: null,
      },
    });

    const results = await this.prisma.$transaction(
      products.map((product) =>
        this.prisma.product.update({
          where: { id: product.id },
          data: {
            canonicalName: product.name,
          },
        }),
      ),
    );

    return {
      message: 'Canonical names backfilled successfully',
      updatedCount: results.length,
      products: results,
    };
  }

  async setTags(productId: number, tagNames: string[]) {
    await this.findOne(productId);

    // Skapa taggar som inte finns och hämta ID för alla
    const tags = await this.prisma.$transaction(
      tagNames.map((name) =>
        this.prisma.tag.upsert({
          where: { name },
          create: { name },
          update: {},
        }),
      ),
    );

    // Ersätt alla taggkopplingar för produkten
    await this.prisma.productTag.deleteMany({ where: { productId } });

    if (tags.length > 0) {
      await this.prisma.productTag.createMany({
        data: tags.map((tag) => ({ productId, tagId: tag.id })),
        skipDuplicates: true,
      });
    }

    return this.prisma.product.findUnique({
      where: { id: productId },
      include: { tags: { include: { tag: true } }, nutrition: true },
    });
  }

  async upsertNutrition(productId: number, data: UpsertNutritionDto) {
    await this.findOne(productId);

    return this.prisma.nutrition.upsert({
      where: { productId },
      create: { productId, ...data },
      update: { ...data },
    });
  }

  async findAllTags() {
    return this.prisma.tag.findMany({ orderBy: { name: 'asc' } });
  }

  async resetAll() {
    await this.prisma.$transaction([
      this.prisma.receiptAlias.deleteMany(),
      this.prisma.inventoryConsumption.deleteMany(),
      this.prisma.inventoryItem.deleteMany(),
      this.prisma.pantryItem.deleteMany(),
      this.prisma.nutrition.deleteMany(),
      this.prisma.productTag.deleteMany(),
      this.prisma.tag.deleteMany(),
      this.prisma.userProduct.deleteMany(),
      this.prisma.recipeIngredient.deleteMany(),
      this.prisma.product.deleteMany(),
    ]);
    return { ok: true };
  }

  async bulkUpdate(ids: number[], data: { categoryId?: number | null }) {
    const updateData: Record<string, any> = {};
    if ('categoryId' in data) {
      updateData.categoryId = data.categoryId;
    }
    if (Object.keys(updateData).length === 0) return { updated: 0 };
    await this.prisma.product.updateMany({ where: { id: { in: ids } }, data: updateData });
    return { updated: ids.length };
  }

  async findUncategorized(): Promise<{ id: number; name: string; canonicalName: string | null }[]> {
    return this.prisma.product.findMany({
      where: { isActive: true, categoryId: null, status: 'active' },
      select: { id: true, name: true, canonicalName: true },
      orderBy: { name: 'asc' },
    });
  }

  async aiCategorizeBulk(productIds?: number[]): Promise<{ productId: number; productName: string; suggestion: object }[]> {
    const categories = await this.categoriesService.findFlattened();
    let products: { id: number; name: string }[];

    if (productIds && productIds.length > 0) {
      const found = await Promise.all(productIds.map((id) => this.findOne(id)));
      products = found.map((p) => ({ id: p.id, name: p.canonicalName ?? p.name }));
    } else {
      products = await this.findUncategorized();
    }

    const results: { productId: number; productName: string; suggestion: object }[] = [];
    for (const product of products) {
      const suggestion = await this.aiService.suggestCategory(product.name, categories);
      results.push({ productId: product.id, productName: product.name, suggestion });
    }
    return results;
  }

  async findPending() {
    return this.prisma.product.findMany({
      where: { status: 'pending' },
      include: {
        categoryRef: { include: { parent: true } },
        owner: { select: { id: true, username: true } },
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async createPending(data: CreateProductDto, userId: number) {
    const name = data.name.trim();
    const normalizedName = normalizeName(name);

    const existing = await this.prisma.product.findUnique({
      where: { normalizedName },
    });

    if (existing) {
      // Om produkten redan finns (aktiv), returnera den direkt
      if (existing.isActive && existing.status === 'active') {
        return existing;
      }
      // Om det redan finns ett pending-förslag, returnera det
      if (existing.status === 'pending') {
        return existing;
      }
    }

    return this.prisma.product.create({
      data: {
        name,
        normalizedName,
        canonicalName: name,
        isActive: false,
        status: 'pending',
        ownerId: userId,
      },
    });
  }

  setStatus(id: number, status: string) {
    return this.prisma.product.update({ where: { id }, data: { status } });
  }
}