import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common';
import { Prisma } from '@prisma/client';
import { PrismaService } from '../../../prisma/prisma.service';
import { AuthenticatedUser } from '../../../common/decorators/current-user.decorator';

interface CreateExpenseInput {
  complexId: string;
  supplierId?: string;
  category: string;
  amount: number;
  currency?: string;
  invoiceNumber?: string;
  invoiceDate: Date;
  invoicePdfUrl?: string;
  repartitionKeyId?: string;
  notes?: string;
}

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

  private async assertComplexAccess(user: AuthenticatedUser, complexId: string) {
    const c = await this.prisma.complex.findUnique({ where: { id: complexId } });
    if (!c) throw new NotFoundException('Complex not found');
    if (!user.isSuperAdmin && c.tenantId !== user.tenantId) throw new ForbiddenException();
    return c;
  }

  async create(user: AuthenticatedUser, dto: CreateExpenseInput) {
    await this.assertComplexAccess(user, dto.complexId);
    return this.prisma.expense.create({
      data: {
        complexId: dto.complexId,
        supplierId: dto.supplierId,
        category: dto.category,
        amount: new Prisma.Decimal(dto.amount),
        currency: dto.currency ?? 'MAD',
        invoiceNumber: dto.invoiceNumber,
        invoiceDate: dto.invoiceDate,
        invoicePdfUrl: dto.invoicePdfUrl,
        repartitionKeyId: dto.repartitionKeyId,
        notes: dto.notes,
      },
    });
  }

  async list(user: AuthenticatedUser, complexId: string) {
    await this.assertComplexAccess(user, complexId);
    return this.prisma.expense.findMany({
      where: { complexId },
      orderBy: { invoiceDate: 'desc' },
      include: { supplier: true, repartitionKey: true },
    });
  }

  async findOne(user: AuthenticatedUser, id: string) {
    const e = await this.prisma.expense.findUnique({
      where: { id },
      include: { supplier: true, repartitionKey: true, complex: true },
    });
    if (!e) throw new NotFoundException('Expense not found');
    if (!user.isSuperAdmin && e.complex.tenantId !== user.tenantId) throw new ForbiddenException();
    return e;
  }

  async remove(user: AuthenticatedUser, id: string) {
    await this.findOne(user, id);
    await this.prisma.expense.delete({ where: { id } });
    return { deleted: true };
  }

  async budgetConsumption(user: AuthenticatedUser, complexId: string) {
    await this.assertComplexAccess(user, complexId);

    // Find the current open fiscal year for budget lines
    const fiscalYear = await this.prisma.fiscalYear.findFirst({
      where: { complexId, status: 'OPEN' },
      include: { budgets: { include: { lines: true } } },
      orderBy: { startDate: 'desc' },
    });

    // Expense totals by category
    const expenses = await this.prisma.expense.groupBy({
      by: ['category'],
      where: { complexId },
      _sum: { amount: true },
    });
    const spentMap = new Map(expenses.map((e) => [e.category, Number(e._sum.amount ?? 0)]));

    // Budget lines by category
    const budgetLines = fiscalYear?.budgets.flatMap((b) => b.lines) ?? [];
    const estimatedMap = new Map<string, number>();
    for (const line of budgetLines) {
      estimatedMap.set(line.category, (estimatedMap.get(line.category) ?? 0) + Number(line.estimatedAmount));
    }

    // Merge categories from both sources
    const categories = new Set([...spentMap.keys(), ...estimatedMap.keys()]);
    const byCategory = [...categories].map((category) => ({
      category,
      estimated: estimatedMap.get(category) ?? 0,
      spent: spentMap.get(category) ?? 0,
    }));

    const totalBudget = byCategory.reduce((s, c) => s + c.estimated, 0);
    const totalSpent = byCategory.reduce((s, c) => s + c.spent, 0);

    return {
      complexId,
      fiscalYearId: fiscalYear?.id ?? null,
      totalBudget,
      totalSpent,
      byCategory,
    };
  }
}
