// ============================================================
// Lexware Office - Voucher Operations Service
// ============================================================

import {
  Injectable,
  Logger,
  NotFoundException,
  ConflictException,
} from '@nestjs/common';
import { Prisma } from '.prisma/crm-client';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { LexwareClientService } from './lexware-client.service';
import { QueryLexwareVouchersDto } from './dto/query-lexware-vouchers.dto';
import {
  LexwareVoucherListResponse,
  LexwareVoucherDetail,
} from './interfaces/lexware-api.interfaces';
import {
  voucherTypeFromLexware,
  voucherTypeToLexwareEndpoint,
  voucherDetailToCacheData,
} from './utils/lexware-mapper';

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

  constructor(
    private readonly prisma: CrmPrismaService,
    private readonly lexwareClient: LexwareClientService,
  ) {}

  // --------------------------------------------------------
  // Belege von Lexware laden und cachen
  // --------------------------------------------------------

  async fetchAndCacheVouchers(
    tenantId: string,
    lexwareContactId: string,
    companyId?: string | null,
    contactId?: string | null,
  ): Promise<number> {
    this.logger.log(
      `Lade Belege fuer Lexware-Kontakt ${lexwareContactId}...`,
    );

    // Alle relevanten Belegtypen laden
    const voucherTypes = [
      'invoice',
      'quotation',
      'orderconfirmation',
      'creditnote',
    ];
    let totalUpserted = 0;

    for (const voucherType of voucherTypes) {
      let page = 0;
      let hasMore = true;

      while (hasMore) {
        const listResponse =
          await this.lexwareClient.get<LexwareVoucherListResponse>(
            '/v1/voucherlist',
            {
              contactId: lexwareContactId,
              voucherType,
              voucherStatus: 'any',
              page,
              size: 100,
            },
          );

        for (const item of listResponse.content) {
          try {
            // Detail laden fuer vollstaendige Daten
            const crmVoucherType = voucherTypeFromLexware(item.voucherType);
            const endpoint = voucherTypeToLexwareEndpoint(crmVoucherType);
            const detail =
              await this.lexwareClient.get<LexwareVoucherDetail>(
                `/v1/${endpoint}/${item.id}`,
              );

            const cacheData = voucherDetailToCacheData(
              detail,
              crmVoucherType,
              lexwareContactId,
            );

            // Upsert: Existierenden Beleg aktualisieren oder neuen anlegen
            await this.prisma.lexwareVoucher.upsert({
              where: {
                tenantId_lexwareVoucherId: {
                  tenantId,
                  lexwareVoucherId: item.id,
                },
              },
              create: {
                tenantId,
                lexwareVoucherId: item.id,
                ...cacheData,
                companyId: companyId || undefined,
                contactId: contactId || undefined,
                fetchedAt: new Date(),
              },
              update: {
                ...cacheData,
                companyId: companyId || undefined,
                contactId: contactId || undefined,
                fetchedAt: new Date(),
              },
            });

            totalUpserted++;
          } catch (error) {
            this.logger.warn(
              `Fehler beim Laden von Beleg ${item.id}: ${error instanceof Error ? error.message : 'Unbekannt'}`,
            );
          }
        }

        hasMore = !listResponse.last;
        page++;
      }
    }

    this.logger.log(
      `${totalUpserted} Belege fuer Lexware-Kontakt ${lexwareContactId} gecacht.`,
    );
    return totalUpserted;
  }

  // --------------------------------------------------------
  // Belege fuer Company abrufen (aus Cache)
  // --------------------------------------------------------

  async getVouchersForCompany(
    tenantId: string,
    companyId: string,
    query: QueryLexwareVouchersDto,
  ) {
    const company = await this.prisma.company.findFirst({
      where: { id: companyId, tenantId },
    });
    if (!company) {
      throw new NotFoundException('Unternehmen nicht gefunden');
    }

    return this.queryVouchers(tenantId, { companyId }, query);
  }

  // --------------------------------------------------------
  // Belege fuer Contact abrufen (aus Cache)
  // --------------------------------------------------------

  async getVouchersForContact(
    tenantId: string,
    contactId: string,
    query: QueryLexwareVouchersDto,
  ) {
    const contact = await this.prisma.contact.findFirst({
      where: { id: contactId, tenantId },
    });
    if (!contact) {
      throw new NotFoundException('Kontakt nicht gefunden');
    }

    return this.queryVouchers(tenantId, { contactId }, query);
  }

  // --------------------------------------------------------
  // Belege fuer Deal abrufen (ueber DealVoucher Join-Table)
  // --------------------------------------------------------

  async getVouchersForDeal(
    tenantId: string,
    dealId: string,
    query: QueryLexwareVouchersDto,
  ) {
    const deal = await this.prisma.deal.findFirst({
      where: { id: dealId, tenantId },
    });
    if (!deal) {
      throw new NotFoundException('Vorgang nicht gefunden');
    }

    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 25;

    const where: Prisma.DealVoucherWhereInput = {
      tenantId,
      dealId,
    };

    // Filter auf Voucher-Ebene
    const voucherWhere: Prisma.LexwareVoucherWhereInput = {};
    if (query.voucherType) {
      voucherWhere.voucherType = query.voucherType;
    }
    if (query.voucherStatus) {
      voucherWhere.voucherStatus = query.voucherStatus;
    }

    if (Object.keys(voucherWhere).length > 0) {
      where.voucher = voucherWhere;
    }

    const [dealVouchers, total] = await Promise.all([
      this.prisma.dealVoucher.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { linkedAt: 'desc' },
        include: {
          voucher: true,
        },
      }),
      this.prisma.dealVoucher.count({ where }),
    ]);

    return {
      data: dealVouchers.map((dv) => ({
        ...dv.voucher,
        linkedBy: dv.linkedBy,
        linkedAt: dv.linkedAt,
        dealVoucherId: dv.id,
      })),
      total,
      page,
      pageSize,
    };
  }

  // --------------------------------------------------------
  // Beleg mit Deal verknuepfen
  // --------------------------------------------------------

  async linkVoucherToDeal(
    tenantId: string,
    dealId: string,
    voucherId: string,
    userId: string,
  ) {
    // Deal pruefen
    const deal = await this.prisma.deal.findFirst({
      where: { id: dealId, tenantId },
    });
    if (!deal) {
      throw new NotFoundException('Vorgang nicht gefunden');
    }

    // Voucher pruefen
    const voucher = await this.prisma.lexwareVoucher.findFirst({
      where: { id: voucherId, tenantId },
    });
    if (!voucher) {
      throw new NotFoundException('Beleg nicht gefunden');
    }

    // Pruefe ob bereits verknuepft
    const existing = await this.prisma.dealVoucher.findUnique({
      where: { dealId_voucherId: { dealId, voucherId } },
    });
    if (existing) {
      throw new ConflictException('Beleg ist bereits mit diesem Vorgang verknuepft');
    }

    return this.prisma.dealVoucher.create({
      data: {
        tenantId,
        dealId,
        voucherId,
        linkedBy: userId,
      },
      include: {
        voucher: true,
      },
    });
  }

  // --------------------------------------------------------
  // Beleg von Deal trennen
  // --------------------------------------------------------

  async unlinkVoucherFromDeal(
    tenantId: string,
    dealId: string,
    voucherId: string,
  ) {
    const dealVoucher = await this.prisma.dealVoucher.findFirst({
      where: { tenantId, dealId, voucherId },
    });
    if (!dealVoucher) {
      throw new NotFoundException('Verknuepfung nicht gefunden');
    }

    return this.prisma.dealVoucher.delete({
      where: { id: dealVoucher.id },
    });
  }

  // --------------------------------------------------------
  // Cache manuell aktualisieren
  // --------------------------------------------------------

  async refreshCompanyVouchers(tenantId: string, companyId: string) {
    const company = await this.prisma.company.findFirst({
      where: { id: companyId, tenantId },
    });
    if (!company) {
      throw new NotFoundException('Unternehmen nicht gefunden');
    }
    if (!company.lexwareContactId) {
      throw new NotFoundException(
        'Unternehmen ist nicht mit Lexware verknuepft',
      );
    }

    return this.fetchAndCacheVouchers(
      tenantId,
      company.lexwareContactId,
      companyId,
      null,
    );
  }

  async refreshContactVouchers(tenantId: string, contactId: string) {
    const contact = await this.prisma.contact.findFirst({
      where: { id: contactId, tenantId },
    });
    if (!contact) {
      throw new NotFoundException('Kontakt nicht gefunden');
    }
    if (!contact.lexwareContactId) {
      throw new NotFoundException('Kontakt ist nicht mit Lexware verknuepft');
    }

    return this.fetchAndCacheVouchers(
      tenantId,
      contact.lexwareContactId,
      null,
      contactId,
    );
  }

  // --------------------------------------------------------
  // Interne Hilfsfunktion: Paginierte Voucher-Abfrage
  // --------------------------------------------------------

  private async queryVouchers(
    tenantId: string,
    filter: { companyId?: string; contactId?: string },
    query: QueryLexwareVouchersDto,
  ) {
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 25;

    const where: Prisma.LexwareVoucherWhereInput = {
      tenantId,
      ...filter,
    };

    if (query.voucherType) {
      where.voucherType = query.voucherType;
    }
    if (query.voucherStatus) {
      where.voucherStatus = query.voucherStatus;
    }

    const [data, total] = await Promise.all([
      this.prisma.lexwareVoucher.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { voucherDate: 'desc' },
      }),
      this.prisma.lexwareVoucher.count({ where }),
    ]);

    return { data, total, page, pageSize };
  }
}
