import {
  BadRequestException,
  Injectable,
  Logger,
  ServiceUnavailableException,
} from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { ParsedReceiptItem } from './dto/parsed-receipt-item.dto';
import { AiService } from '../ai/ai.service';
import { CategoriesService } from '../categories/categories.service';

const IMPORTER_SERVICE_URL =
  process.env.IMPORTER_SERVICE_URL || 'http://importer-api:3001';

@Injectable()
export class ReceiptImportService {
  private readonly logger = new Logger(ReceiptImportService.name);

  constructor(
    private readonly prisma: PrismaService,
    private readonly aiService: AiService,
    private readonly categoriesService: CategoriesService,
  ) {}

  async parseReceipt(file: Express.Multer.File, isPremium = false): Promise<ParsedReceiptItem[]> {
    // Steg 1: Delegera AI-parsning till microservice-importer
    const rawItems = await this.parseReceiptViaImporter(file);

    // Steg 2: Matchning mot produktdatabas (kräver DB — stannar i recipe-app)
    const matched = await this.matchProducts(rawItems);

    // Steg 3: AI-kategorisering för premium-användare
    if (isPremium) {
      return this.enrichWithAiCategories(matched);
    }
    return matched;
  }

  private async parseReceiptViaImporter(file: Express.Multer.File): Promise<ParsedReceiptItem[]> {
    const form = new FormData();
    form.append(
      'file',
      new Blob([new Uint8Array(file.buffer)], { type: file.mimetype }),
      file.originalname,
    );

    let response: Response;
    try {
      response = await fetch(`${IMPORTER_SERVICE_URL}/api/receipt-import/parse`, {
        method: 'POST',
        body: form,
      });
    } catch (err) {
      this.logger.error(`Kunde inte nå importer-api för kvittoparsning: ${err}`);
      throw new ServiceUnavailableException(
        'Import-tjänsten är inte tillgänglig. Försök igen senare.',
      );
    }

    if (!response.ok) {
      let message = `Importer svarade ${response.status}`;
      try {
        const body = (await response.json()) as { message?: string };
        if (body.message) message = body.message;
      } catch {
        // ignorera parse-fel
      }
      this.logger.error(`Importer-api kvittoparsfel: ${message}`);
      if (response.status >= 400 && response.status < 500) {
        throw new BadRequestException(message);
      }
      throw new ServiceUnavailableException(message);
    }

    return response.json() as Promise<ParsedReceiptItem[]>;
  }

  private async matchProducts(
    items: ParsedReceiptItem[],
  ): Promise<ParsedReceiptItem[]> {
    // Hämta alias och produkter parallellt
    const [aliases, products] = await Promise.all([
      this.prisma.receiptAlias.findMany({
        select: { receiptName: true, productId: true, product: { select: { id: true, name: true, canonicalName: true } } },
      }),
      this.prisma.product.findMany({
        where: { isActive: true },
        select: { id: true, name: true, canonicalName: true },
      }),
    ]);

    return items.map((item) => {
      const raw = (item.rawName ?? '').toLowerCase().trim();
      if (!raw) return item;

      // 1. Alias-match (säker, användaren behöver inte bekräfta)
      const alias = aliases.find((a) => a.receiptName === raw);
      if (alias) {
        return {
          ...item,
          matchedProductId: alias.product.id,
          matchedProductName: alias.product.canonicalName ?? alias.product.name,
        };
      }

      // 2. Ordbaserad matchning (förslag, kräver bekräftelse)
      const suggestion = this.findWordMatch(raw, products);
      return {
        ...item,
        suggestedProductId: suggestion?.id,
        suggestedProductName: suggestion
          ? (suggestion.canonicalName ?? suggestion.name)
          : undefined,
      };
    });
  }

  private findWordMatch(
    raw: string,
    products: { id: number; name: string; canonicalName: string | null }[],
  ): { id: number; name: string; canonicalName: string | null } | undefined {
    // Dela upp kvittonamnet i ord (min 3 tecken)
    const rawWords = raw.split(/[\s\-_]+/).filter((w) => w.length >= 3);
    if (rawWords.length === 0) return undefined;

    // Fortsätt med att hitta produkter där ett produktnamn-ord finns i kvittonamnet
    // Exempel: produktord "ost" finns i kvittoord "prästost", "herrgårdsost", "brieost"
    return products.find((p) => {
      const productWords = (p.canonicalName ?? p.name)
        .toLowerCase()
        .split(/[\s\-_]+/)
        .filter((w) => w.length >= 3);
      return productWords.some((pw) =>
        rawWords.some((rw) => rw.includes(pw) || pw.includes(rw)),
      );
    });
  }

  private async enrichWithAiCategories(items: ParsedReceiptItem[]): Promise<ParsedReceiptItem[]> {
    const unmatched = items.filter((i) => !i.matchedProductId && !i.suggestedProductId && i.rawName);
    if (unmatched.length === 0) return items;

    let categories: Awaited<ReturnType<CategoriesService['findFlattened']>>;
    try {
      categories = await this.categoriesService.findFlattened();
    } catch {
      return items; // Om kategoritjänsten är otillgänglig, returnera utan AI-förslag
    }

    const enriched = new Map<string, ParsedReceiptItem>();
    for (const item of unmatched) {
      try {
        const suggestion = await this.aiService.suggestCategory(item.rawName, categories);
        enriched.set(item.rawName, { ...item, categorySuggestion: suggestion });
      } catch {
        // Om AI-anrop misslyckas för enskild vara — hoppa över utan att kasta
        enriched.set(item.rawName, item);
      }
    }

    return items.map((item) => enriched.get(item.rawName) ?? item);
  }
}
