/**
 * Invoices Service
 *
 * Replaces: LastInvoice, HoldSInvoice, DoInvoice, UnDoInvoice, etc. from TAmzServerImpl
 * Tables: fat_sale, fat_buy, fat_bback, fat_sback, posl
 */
import { Injectable, BadRequestException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AccountingService } from '../accounting/accounting.service';
import { InventoryService } from '../inventory/inventory.service';

export interface FatSaleLineDto {
  ref: number;
  dftrNo: number;
  sfatNo: number;
  sfatSq: number;
  dates?: Date;
  dailyActNo?: string;
  dayMg?: string;
  dayMt?: string;
  sfatMtnm?: string;
  qnTtin?: number;
  sfatPric?: number;
  monVl?: number;
  curMs?: number;
  curDs?: number;
}

export interface FatSaleHeaderDto {
  sfatTyp?: number;
  sfatBrnch?: number;
  sfatMndb?: number;
  sfatKlfn?: number;
  sfatSalr?: number;
  sfatExprt?: number;
  sfatMncd?: number;
  sfatSlwy?: number;
  sfatPaym?: number;
}

@Injectable()
export class InvoicesService {
  constructor(
    private prisma: PrismaService,
    private accountingService: AccountingService,
    private inventoryService: InventoryService,
  ) {}

  /**
   * Last sale invoice number
   * Maps to: LastInvoice(dft, st, ref)
   */
  async getLastSaleInvoice(
    dft: number,
    st: number,
    ref: number,
  ): Promise<number> {
    const result = await this.prisma.fatSale.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    if (max === 0) return st > 0 ? st : 1;
    return max + 1;
  }

  /**
   * Last buy invoice number
   * Maps to: LastBuyInvoice(dft, st, ref)
   */
  async getLastBuyInvoice(
    dft: number,
    st: number,
    ref: number,
  ): Promise<number> {
    const result = await this.prisma.fatBuy.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    if (max === 0) return st > 0 ? st : 1;
    return max + 1;
  }

  /**
   * Hold (count) sale invoices for dft/ref
   * Maps to: HoldSInvoice(dft, st, ref)
   */
  async holdSaleInvoice(dft: number, ref: number): Promise<number> {
    return this.prisma.fatSale.count({
      where: { dftrNo: dft, ref },
    });
  }

  /**
   * Hold (count) buy invoices for dft/ref
   */
  async holdBuyInvoice(dft: number, ref: number): Promise<number> {
    return this.prisma.fatBuy.count({
      where: { dftrNo: dft, ref },
    });
  }

  // --- BBack, SBack, Posl, ExInvoice ---
  async getLastBBack(dft: number, st: number, ref: number): Promise<number> {
    const result = await this.prisma.fatBback.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    return max === 0 ? (st > 0 ? st : 1) : max + 1;
  }

  async getLastSBack(dft: number, st: number, ref: number): Promise<number> {
    const result = await this.prisma.fatSback.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    return max === 0 ? (st > 0 ? st : 1) : max + 1;
  }

  async getLastPosl(dft: number, st: number, ref: number): Promise<number> {
    const result = await this.prisma.posl.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    return max === 0 ? (st > 0 ? st : 1) : max + 1;
  }

  async getLastExInvoice(dft: number, st: number, ref: number): Promise<number> {
    const result = await this.prisma.fatEx.aggregate({
      where: { dftrNo: dft, ref },
      _max: { sfatNo: true },
    });
    const max = result._max.sfatNo ?? 0;
    return max === 0 ? (st > 0 ? st : 1) : max + 1;
  }

  async holdBBackInvoice(dft: number, ref: number): Promise<number> {
    return this.prisma.fatBback.count({ where: { dftrNo: dft, ref } });
  }

  async holdSBackInvoice(dft: number, ref: number): Promise<number> {
    return this.prisma.fatSback.count({ where: { dftrNo: dft, ref } });
  }

  async holdPosl(dft: number, ref: number): Promise<number> {
    return this.prisma.posl.count({ where: { dftrNo: dft, ref } });
  }

  async holdExInvoice(dft: number, ref: number): Promise<number> {
    return this.prisma.fatEx.count({ where: { dftrNo: dft, ref } });
  }

  /**
   * DoInvoice(dft, N, ref) - Post sale invoice: DoAcount(acn,1,ref) + DoMG(mgn,1,ref)
   * acn/mgn from fat_sale sfat_sndac/sfat_sndmg (header row sfat_sq=99000 or first row)
   */
  async doSaleInvoice(dft: number, n: number, ref: number): Promise<{ success: boolean }> {
    const row = await this.prisma.fatSale.findFirst({
      where: { dftrNo: dft, sfatNo: n, ref },
      orderBy: { sfatSq: 'asc' },
    });
    if (!row) {
      throw new BadRequestException(`No fat_sale for dft=${dft}, n=${n}, ref=${ref}`);
    }
    const acn = row.sfatSndac ?? 0;
    const mgn = row.sfatSndmg ?? 0;
    if (mgn !== 0) await this.inventoryService.doMG(mgn, 1, ref);
    if (acn !== 0) await this.accountingService.doAcount(acn, 1, ref);
    return { success: true };
  }

  /**
   * UnDoInvoice(dft, N, ref) - Unpost sale invoice
   */
  async unDoSaleInvoice(dft: number, n: number, ref: number): Promise<{ success: boolean }> {
    const row = await this.prisma.fatSale.findFirst({
      where: { dftrNo: dft, sfatNo: n, ref },
      orderBy: { sfatSq: 'asc' },
    });
    if (!row) {
      throw new BadRequestException(`No fat_sale for dft=${dft}, n=${n}, ref=${ref}`);
    }
    const acn = row.sfatSndac ?? 0;
    const mgn = row.sfatSndmg ?? 0;
    if (mgn !== 0) await this.inventoryService.unDoMG(mgn, 1, ref);
    if (acn !== 0) await this.accountingService.unDoAcount(acn, 1, ref);
    return { success: true };
  }

  /**
   * DoBuyInvoice(dft, N, ref) - Post buy invoice
   */
  async doBuyInvoice(dft: number, n: number, ref: number): Promise<{ success: boolean }> {
    const row = await this.prisma.fatBuy.findFirst({
      where: { dftrNo: dft, sfatNo: n, ref },
      orderBy: { sfatSq: 'asc' },
    });
    if (!row) {
      throw new BadRequestException(`No fat_buy for dft=${dft}, n=${n}, ref=${ref}`);
    }
    const acn = row.sfatSndac ?? 0;
    const mgn = row.sfatSndmg ?? 0;
    if (mgn !== 0) await this.inventoryService.doMG(mgn, 1, ref);
    if (acn !== 0) await this.accountingService.doAcount(acn, 1, ref);
    return { success: true };
  }

  /**
   * UnDoBuyInvoice(dft, N, ref) - Unpost buy invoice
   */
  async unDoBuyInvoice(dft: number, n: number, ref: number): Promise<{ success: boolean }> {
    const row = await this.prisma.fatBuy.findFirst({
      where: { dftrNo: dft, sfatNo: n, ref },
      orderBy: { sfatSq: 'asc' },
    });
    if (!row) {
      throw new BadRequestException(`No fat_buy for dft=${dft}, n=${n}, ref=${ref}`);
    }
    const acn = row.sfatSndac ?? 0;
    const mgn = row.sfatSndmg ?? 0;
    if (mgn !== 0) await this.inventoryService.unDoMG(mgn, 1, ref);
    if (acn !== 0) await this.accountingService.unDoAcount(acn, 1, ref);
    return { success: true };
  }

  // --- Sale Invoice CRUD (SSinvc) ---

  async getSaleInvoiceLines(dft: number, n: number, ref: number) {
    return this.prisma.fatSale.findMany({
      where: { dftrNo: dft, sfatNo: n, ref },
      orderBy: { sfatSq: 'asc' },
    });
  }

  async listSaleInvoices(
    dft: number,
    ref: number,
    sfatTyp?: number,
  ) {
    const where: { dftrNo: number; ref: number; sfatTyp?: number } = {
      dftrNo: dft,
      ref,
    };
    if (sfatTyp !== undefined && sfatTyp !== null) {
      where.sfatTyp = sfatTyp;
    }
    const rows = await this.prisma.fatSale.findMany({
      where,
      distinct: ['sfatNo'],
      select: {
        sfatNo: true,
        dates: true,
        dailyActNo: true,
        sfatTyp: true,
        sfatBrnch: true,
        sfatMndb: true,
        sfatPaym: true,
      },
      orderBy: [{ sfatNo: 'desc' }],
      take: 100,
    });
    return rows;
  }

  async createOrUpdateSaleInvoice(
    dft: number,
    n: number,
    ref: number,
    lines: FatSaleLineDto[],
    header?: FatSaleHeaderDto,
  ) {
    const existing = await this.prisma.fatSale.findMany({
      where: { dftrNo: dft, sfatNo: n, ref },
    });
    const existingKeys = new Set(
      existing.map((r) => `${r.ref}-${r.dftrNo}-${r.sfatNo}-${r.sfatSq}`),
    );

    const pendingTyp = header?.sfatTyp ?? 1;
    for (const line of lines) {
      const sq = line.sfatSq;
      const data = {
        ref,
        dftrNo: dft,
        sfatNo: n,
        sfatSq: sq,
        dates: line.dates ?? new Date(),
        dailyActNo: line.dailyActNo ?? null,
        dayMg: line.dayMg ?? null,
        dayMt: line.dayMt ?? null,
        sfatMtnm: line.sfatMtnm ?? null,
        qnTtin: line.qnTtin ?? 0,
        sfatPric: line.sfatPric ?? null,
        monVl: line.monVl ?? 0,
        curMs: line.curMs ?? 0,
        curDs: line.curDs ?? 0,
        sfatTyp: pendingTyp,
        sfatBrnch: header?.sfatBrnch ?? null,
        sfatMndb: header?.sfatMndb ?? null,
        sfatKlfn: header?.sfatKlfn ?? null,
        sfatSalr: header?.sfatSalr ?? null,
        sfatExprt: header?.sfatExprt ?? null,
        sfatMncd: header?.sfatMncd ?? null,
        sfatSlwy: header?.sfatSlwy ?? null,
        sfatPaym: header?.sfatPaym ?? null,
      };
      await this.prisma.fatSale.upsert({
        where: {
          ref_dftrNo_sfatNo_sfatSq: { ref, dftrNo: dft, sfatNo: n, sfatSq: sq },
        },
        create: data,
        update: data,
      });
      existingKeys.delete(`${ref}-${dft}-${n}-${sq}`);
    }

    const toDelete = Array.from(existingKeys)
      .map((k) => k.split('-').map(Number))
      .filter(([r, d, no]) => r === ref && d === dft && no === n);
    for (const [,,, sq] of toDelete) {
      await this.prisma.fatSale.delete({
        where: {
          ref_dftrNo_sfatNo_sfatSq: { ref, dftrNo: dft, sfatNo: n, sfatSq: sq },
        },
      });
    }

    return this.getSaleInvoiceLines(dft, n, ref);
  }

  async deleteSaleInvoice(dft: number, n: number, ref: number) {
    await this.prisma.fatSale.deleteMany({
      where: { dftrNo: dft, sfatNo: n, ref },
    });
    return { success: true };
  }
}
