// ============================================================
// Lexware Office - Contact Operations Service
// ============================================================

import {
  Injectable,
  Logger,
  NotFoundException,
  ConflictException,
  BadRequestException,
} from '@nestjs/common';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { LexwareClientService } from './lexware-client.service';
import { SearchLexwareContactsDto } from './dto/search-lexware-contacts.dto';
import {
  LexwareContact,
  LexwareContactListResponse,
  LexwareContactCreateResponse,
} from './interfaces/lexware-api.interfaces';
import {
  lexwareContactToCompanyData,
  lexwareContactToContactData,
  companyToLexwareContactBody,
  contactToLexwareContactBody,
} from './utils/lexware-mapper';

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

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

  // --------------------------------------------------------
  // Suche in Lexware Office
  // --------------------------------------------------------

  async searchContacts(
    query: SearchLexwareContactsDto,
  ): Promise<LexwareContactListResponse> {
    const params: Record<string, string | number | boolean> = {};

    if (query.name) params.name = query.name;
    if (query.email) params.email = query.email;
    if (query.number !== undefined) params.number = query.number;
    if (query.customer !== undefined) params.customer = query.customer;
    if (query.vendor !== undefined) params.vendor = query.vendor;
    if (query.page !== undefined) params.page = query.page;
    if (query.size !== undefined) params.size = query.size;

    return this.lexwareClient.get<LexwareContactListResponse>(
      '/v1/contacts',
      params,
    );
  }

  async getContact(lexwareContactId: string): Promise<LexwareContact> {
    return this.lexwareClient.get<LexwareContact>(
      `/v1/contacts/${lexwareContactId}`,
    );
  }

  // --------------------------------------------------------
  // Link Lexware Contact -> CRM Company
  // --------------------------------------------------------

  async linkToCompany(
    tenantId: string,
    companyId: string,
    lexwareContactId: string,
    userId: string,
  ) {
    // Pruefe ob Company existiert
    const company = await this.prisma.company.findFirst({
      where: { id: companyId, tenantId },
    });
    if (!company) {
      throw new NotFoundException('Unternehmen nicht gefunden');
    }

    // Pruefe ob Lexware-Kontakt bereits verknuepft
    const existing = await this.prisma.company.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existing && existing.id !== companyId) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Unternehmen "${existing.name}" verknuepft`,
      );
    }

    // Auch bei Contacts pruefen
    const existingContact = await this.prisma.contact.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existingContact) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Kontakt "${existingContact.firstName} ${existingContact.lastName}" verknuepft`,
      );
    }

    // Lexware-Kontakt laden und Version speichern
    const lexwareContact = await this.getContact(lexwareContactId);

    return this.prisma.company.update({
      where: { id: companyId },
      data: {
        lexwareContactId,
        lexwareContactVersion: lexwareContact.version,
        lexwareSyncedAt: new Date(),
        updatedBy: userId,
      },
    });
  }

  // --------------------------------------------------------
  // Link Lexware Contact -> CRM Contact
  // --------------------------------------------------------

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

    // Pruefe ob Lexware-Kontakt bereits verknuepft
    const existingContact = await this.prisma.contact.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existingContact && existingContact.id !== contactId) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Kontakt "${existingContact.firstName} ${existingContact.lastName}" verknuepft`,
      );
    }

    const existingCompany = await this.prisma.company.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existingCompany) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Unternehmen "${existingCompany.name}" verknuepft`,
      );
    }

    const lexwareContact = await this.getContact(lexwareContactId);

    return this.prisma.contact.update({
      where: { id: contactId },
      data: {
        lexwareContactId,
        lexwareContactVersion: lexwareContact.version,
        lexwareSyncedAt: new Date(),
        updatedBy: userId,
      },
    });
  }

  // --------------------------------------------------------
  // Unlink
  // --------------------------------------------------------

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

    return this.prisma.company.update({
      where: { id: companyId },
      data: {
        lexwareContactId: null,
        lexwareContactVersion: null,
        lexwareSyncedAt: null,
        updatedBy: userId,
      },
    });
  }

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

    return this.prisma.contact.update({
      where: { id: contactId },
      data: {
        lexwareContactId: null,
        lexwareContactVersion: null,
        lexwareSyncedAt: null,
        updatedBy: userId,
      },
    });
  }

  // --------------------------------------------------------
  // Import: Lexware -> Neue CRM Entity
  // --------------------------------------------------------

  async importAsCompany(
    tenantId: string,
    lexwareContactId: string,
    userId: string,
  ) {
    if (!tenantId) {
      throw new BadRequestException(
        'tenantId fehlt. Bitte mit einem mandanten-gebundenen Account anmelden.',
      );
    }

    // Pruefe ob bereits verknuepft
    const existing = await this.prisma.company.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existing) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Unternehmen "${existing.name}" verknuepft`,
      );
    }

    const lexwareContact = await this.getContact(lexwareContactId);
    const companyData = lexwareContactToCompanyData(lexwareContact);
    const { emails, phones, ...scalarData } = companyData;

    return this.prisma.company.create({
      data: {
        tenantId,
        ...scalarData,
        lexwareContactId,
        lexwareContactVersion: lexwareContact.version,
        lexwareSyncedAt: new Date(),
        createdBy: userId,
        emails: emails.length > 0 ? {
          create: emails.map((e) => ({
            email: e.email,
            type: e.type,
            isPrimary: e.isPrimary,
          })),
        } : undefined,
        phones: phones.length > 0 ? {
          create: phones.map((p) => ({
            phone: p.phone,
            type: p.type,
            isPrimary: p.isPrimary,
          })),
        } : undefined,
        owners: {
          create: { tenantId, userId, role: 'OWNER' },
        },
      },
      include: {
        emails: true,
        phones: true,
        owners: true,
        _count: { select: { contacts: true, deals: true } },
      },
    });
  }

  async importAsContact(
    tenantId: string,
    lexwareContactId: string,
    userId: string,
  ) {
    if (!tenantId) {
      throw new BadRequestException(
        'tenantId fehlt. Bitte mit einem mandanten-gebundenen Account anmelden.',
      );
    }

    const existing = await this.prisma.contact.findFirst({
      where: { tenantId, lexwareContactId },
    });
    if (existing) {
      throw new ConflictException(
        `Lexware-Kontakt ist bereits mit Kontakt "${existing.firstName} ${existing.lastName}" verknuepft`,
      );
    }

    const lexwareContact = await this.getContact(lexwareContactId);
    const contactData = lexwareContactToContactData(lexwareContact);
    const { emails, phones, ...scalarData } = contactData;

    return this.prisma.contact.create({
      data: {
        tenantId,
        ...scalarData,
        lexwareContactId,
        lexwareContactVersion: lexwareContact.version,
        lexwareSyncedAt: new Date(),
        createdBy: userId,
        emails: emails.length > 0 ? {
          create: emails.map((e) => ({
            email: e.email,
            type: e.type,
            isPrimary: e.isPrimary,
          })),
        } : undefined,
        phones: phones.length > 0 ? {
          create: phones.map((p) => ({
            phone: p.phone,
            type: p.type,
            isPrimary: p.isPrimary,
          })),
        } : undefined,
        owners: {
          create: { tenantId, userId, role: 'OWNER' },
        },
      },
      include: {
        emails: true,
        phones: true,
        owners: true,
      },
    });
  }

  // --------------------------------------------------------
  // Push: CRM -> Lexware (ERP-Tag)
  // --------------------------------------------------------

  async pushCompanyToLexware(tenantId: string, companyId: string) {
    const company = await this.prisma.company.findFirst({
      where: { id: companyId, tenantId },
      include: { emails: true, phones: true },
    });
    if (!company) {
      throw new NotFoundException('Unternehmen nicht gefunden');
    }

    const body = companyToLexwareContactBody(company);

    if (company.lexwareContactId) {
      // Update: Hole aktuelle Version von Lexware
      const current = await this.getContact(company.lexwareContactId);
      const updateBody = { ...body, version: current.version };

      try {
        await this.lexwareClient.put(
          `/v1/contacts/${company.lexwareContactId}`,
          updateBody,
        );
      } catch (error: unknown) {
        const axiosError = error as { response?: { status?: number } };
        if (axiosError.response?.status === 409) {
          this.logger.warn(
            `Optimistic Locking Konflikt beim Update von Lexware-Kontakt ${company.lexwareContactId}. Ueberspringe.`,
          );
          return company;
        }
        throw error;
      }

      return this.prisma.company.update({
        where: { id: companyId },
        data: {
          lexwareContactVersion: current.version + 1,
          lexwareSyncedAt: new Date(),
        },
      });
    } else {
      // Create: Neuen Lexware-Kontakt erstellen
      const result =
        await this.lexwareClient.post<LexwareContactCreateResponse>(
          '/v1/contacts',
          body,
        );

      return this.prisma.company.update({
        where: { id: companyId },
        data: {
          lexwareContactId: result.id,
          lexwareContactVersion: result.version,
          lexwareSyncedAt: new Date(),
        },
      });
    }
  }

  async pushContactToLexware(tenantId: string, contactId: string) {
    const contact = await this.prisma.contact.findFirst({
      where: { id: contactId, tenantId },
      include: { emails: true, phones: true },
    });
    if (!contact) {
      throw new NotFoundException('Kontakt nicht gefunden');
    }

    const body = contactToLexwareContactBody(contact);

    if (contact.lexwareContactId) {
      const current = await this.getContact(contact.lexwareContactId);
      const updateBody = { ...body, version: current.version };

      try {
        await this.lexwareClient.put(
          `/v1/contacts/${contact.lexwareContactId}`,
          updateBody,
        );
      } catch (error: unknown) {
        const axiosError = error as { response?: { status?: number } };
        if (axiosError.response?.status === 409) {
          this.logger.warn(
            `Optimistic Locking Konflikt beim Update von Lexware-Kontakt ${contact.lexwareContactId}. Ueberspringe.`,
          );
          return contact;
        }
        throw error;
      }

      return this.prisma.contact.update({
        where: { id: contactId },
        data: {
          lexwareContactVersion: current.version + 1,
          lexwareSyncedAt: new Date(),
        },
      });
    } else {
      const result =
        await this.lexwareClient.post<LexwareContactCreateResponse>(
          '/v1/contacts',
          body,
        );

      return this.prisma.contact.update({
        where: { id: contactId },
        data: {
          lexwareContactId: result.id,
          lexwareContactVersion: result.version,
          lexwareSyncedAt: new Date(),
        },
      });
    }
  }

  // --------------------------------------------------------
  // Sync: Lexware -> CRM (Daten aktualisieren)
  // --------------------------------------------------------

  async syncCompanyFromLexware(
    tenantId: string,
    companyId: string,
    userId: 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',
      );
    }

    const lexwareContact = await this.getContact(company.lexwareContactId);
    const companyData = lexwareContactToCompanyData(lexwareContact);
    const { emails, phones, ...scalarData } = companyData;

    // Transaction: Scalar-Update + emails/phones Replace
    return this.prisma.$transaction(async (tx) => {
      // Bestehende emails/phones loeschen
      await tx.contactEmail.deleteMany({ where: { companyId } });
      await tx.contactPhone.deleteMany({ where: { companyId } });

      // Neue emails/phones anlegen
      if (emails.length > 0) {
        await tx.contactEmail.createMany({
          data: emails.map((e) => ({
            companyId,
            email: e.email,
            type: e.type,
            isPrimary: e.isPrimary,
          })),
        });
      }
      if (phones.length > 0) {
        await tx.contactPhone.createMany({
          data: phones.map((p) => ({
            companyId,
            phone: p.phone,
            type: p.type,
            isPrimary: p.isPrimary,
          })),
        });
      }

      return tx.company.update({
        where: { id: companyId },
        data: {
          ...scalarData,
          lexwareContactVersion: lexwareContact.version,
          lexwareSyncedAt: new Date(),
          updatedBy: userId,
        },
        include: {
          emails: true,
          phones: true,
        },
      });
    });
  }

  async syncContactFromLexware(
    tenantId: string,
    contactId: string,
    userId: 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');
    }

    const lexwareContact = await this.getContact(contact.lexwareContactId);
    const contactData = lexwareContactToContactData(lexwareContact);
    const { emails, phones, ...scalarData } = contactData;

    // Transaction: Scalar-Update + emails/phones Replace
    return this.prisma.$transaction(async (tx) => {
      // Bestehende emails/phones loeschen
      await tx.contactEmail.deleteMany({ where: { contactId } });
      await tx.contactPhone.deleteMany({ where: { contactId } });

      // Neue emails/phones anlegen
      if (emails.length > 0) {
        await tx.contactEmail.createMany({
          data: emails.map((e) => ({
            contactId,
            email: e.email,
            type: e.type,
            isPrimary: e.isPrimary,
          })),
        });
      }
      if (phones.length > 0) {
        await tx.contactPhone.createMany({
          data: phones.map((p) => ({
            contactId,
            phone: p.phone,
            type: p.type,
            isPrimary: p.isPrimary,
          })),
        });
      }

      return tx.contact.update({
        where: { id: contactId },
        data: {
          ...scalarData,
          lexwareContactVersion: lexwareContact.version,
          lexwareSyncedAt: new Date(),
          updatedBy: userId,
        },
        include: {
          emails: true,
          phones: true,
        },
      });
    });
  }
}
