import {
  Injectable,
  Logger,
  BadRequestException,
  NotFoundException,
} from '@nestjs/common';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { ImportEntityType } from './dto/import-preview.dto';
import {
  ImportExecuteDto,
  DuplicateStrategy,
  FieldMappingDto,
} from './dto/import-execute.dto';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
import { v4 as uuidv4 } from 'uuid';
import csvParser from 'csv-parser';
import * as XLSX from 'xlsx';
import { Readable } from 'stream';

const MAX_ROWS = 5000;
const PREVIEW_ROWS = 10;

const TARGET_FIELDS: Record<ImportEntityType, string[]> = {
  [ImportEntityType.CONTACT]: [
    'firstName', 'lastName', 'email', 'phone', 'mobile',
    'companyName', 'position', 'department', 'website',
    'street', 'zip', 'city', 'state', 'country',
    'notes', 'tags', 'source', 'linkedinUrl',
  ],
  [ImportEntityType.COMPANY]: [
    'name', 'email', 'phone', 'website', 'industry',
    'street', 'zip', 'city', 'state', 'country',
    'vatId', 'taxId', 'tradeRegisterNumber', 'registerCourt',
    'notes', 'tags',
  ],
};

interface ParsedFile {
  columns: string[];
  rows: Record<string, string>[];
  totalRows: number;
  format: 'csv' | 'xlsx';
}

export interface ImportError {
  row: number;
  field: string;
  value: string;
  message: string;
}

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

  constructor(private readonly prisma: CrmPrismaService) {}

  // --------------------------------------------------------
  // Preview: Datei parsen und Vorschau zurueckgeben
  // --------------------------------------------------------

  async preview(
    file: Express.Multer.File,
    entityType: ImportEntityType,
    delimiter?: string,
  ) {
    const ext = path.extname(file.originalname).toLowerCase();
    if (!['.csv', '.xlsx', '.xls'].includes(ext)) {
      throw new BadRequestException(
        'Nicht unterstuetztes Dateiformat. Erlaubt: .csv, .xlsx, .xls',
      );
    }

    const parsed = ext === '.csv'
      ? await this.parseCsv(file.buffer, delimiter)
      : this.parseExcel(file.buffer);

    if (parsed.totalRows > MAX_ROWS) {
      throw new BadRequestException(
        `Datei enthaelt ${parsed.totalRows} Zeilen. Maximum: ${MAX_ROWS}`,
      );
    }

    // Temp-Datei speichern (GDPR: wird nach Execute geloescht)
    const importId = uuidv4();
    const tmpPath = path.join(os.tmpdir(), `crm-import-${importId}${ext}`);
    fs.writeFileSync(tmpPath, file.buffer);

    return {
      importId,
      format: parsed.format,
      columns: parsed.columns,
      rows: parsed.rows.slice(0, PREVIEW_ROWS),
      totalRows: parsed.totalRows,
      availableTargetFields: TARGET_FIELDS[entityType],
    };
  }

  // --------------------------------------------------------
  // Execute: Import ausfuehren
  // --------------------------------------------------------

  async execute(tenantId: string, userId: string, dto: ImportExecuteDto) {
    const tmpDir = os.tmpdir();
    const possibleExts = ['.csv', '.xlsx', '.xls'];
    let tmpPath: string | null = null;
    let ext = '';

    for (const e of possibleExts) {
      const candidate = path.join(tmpDir, `crm-import-${dto.importId}${e}`);
      if (fs.existsSync(candidate)) {
        tmpPath = candidate;
        ext = e;
        break;
      }
    }

    if (!tmpPath) {
      throw new NotFoundException(
        'Import-Datei nicht gefunden. Bitte erneut hochladen (Preview abgelaufen).',
      );
    }

    try {
      const buffer = fs.readFileSync(tmpPath);
      const parsed = ext === '.csv'
        ? await this.parseCsv(buffer)
        : this.parseExcel(buffer);

      this.validateMapping(dto.mapping, dto.entityType);

      const result = dto.entityType === ImportEntityType.CONTACT
        ? await this.importContacts(tenantId, userId, parsed.rows, dto.mapping, dto.duplicateStrategy ?? DuplicateStrategy.SKIP)
        : await this.importCompanies(tenantId, userId, parsed.rows, dto.mapping, dto.duplicateStrategy ?? DuplicateStrategy.SKIP);

      return result;
    } finally {
      // GDPR: Temp-Datei immer loeschen
      try {
        fs.unlinkSync(tmpPath);
        this.logger.debug(`Temp-Datei geloescht: ${tmpPath}`);
      } catch {
        this.logger.warn(`Temp-Datei konnte nicht geloescht werden: ${tmpPath}`);
      }
    }
  }

  // --------------------------------------------------------
  // Privat: CSV parsen
  // --------------------------------------------------------

  private parseCsv(buffer: Buffer, delimiter?: string): Promise<ParsedFile> {
    return new Promise((resolve, reject) => {
      const rows: Record<string, string>[] = [];
      let columns: string[] = [];

      const stream = Readable.from(buffer);
      stream
        .pipe(csvParser({
          separator: delimiter || undefined,
          mapHeaders: ({ header }: { header: string }) => header.trim(),
        }))
        .on('headers', (headers: string[]) => {
          columns = headers;
        })
        .on('data', (row: Record<string, string>) => {
          if (rows.length < MAX_ROWS) {
            rows.push(row);
          }
        })
        .on('end', () => {
          resolve({ columns, rows, totalRows: rows.length, format: 'csv' });
        })
        .on('error', (err: Error) => {
          reject(new BadRequestException(`CSV-Parse-Fehler: ${err.message}`));
        });
    });
  }

  // --------------------------------------------------------
  // Privat: Excel parsen
  // --------------------------------------------------------

  private parseExcel(buffer: Buffer): ParsedFile {
    const workbook = XLSX.read(buffer, { type: 'buffer' });
    const sheetName = workbook.SheetNames[0];
    if (!sheetName) {
      throw new BadRequestException('Excel-Datei enthaelt keine Arbeitsblaetter');
    }

    const sheet = workbook.Sheets[sheetName];
    const jsonData = XLSX.utils.sheet_to_json<Record<string, string>>(sheet, {
      defval: '',
      raw: false,
    });

    if (jsonData.length === 0) {
      throw new BadRequestException('Excel-Datei enthaelt keine Daten');
    }

    const columns = Object.keys(jsonData[0]);
    const rows = jsonData.slice(0, MAX_ROWS);

    return { columns, rows, totalRows: jsonData.length, format: 'xlsx' };
  }

  // --------------------------------------------------------
  // Privat: Mapping validieren
  // --------------------------------------------------------

  private validateMapping(
    mapping: FieldMappingDto[],
    entityType: ImportEntityType,
  ): void {
    const allowed = TARGET_FIELDS[entityType];
    for (const m of mapping) {
      if (!allowed.includes(m.targetField)) {
        throw new BadRequestException(
          `Ungueltiges Zielfeld "${m.targetField}" fuer ${entityType}. Erlaubt: ${allowed.join(', ')}`,
        );
      }
    }
  }

  // --------------------------------------------------------
  // Privat: Kontakte importieren
  // --------------------------------------------------------

  private async importContacts(
    tenantId: string,
    userId: string,
    rows: Record<string, string>[],
    mapping: FieldMappingDto[],
    duplicateStrategy: DuplicateStrategy,
  ) {
    let created = 0;
    let updated = 0;
    let skipped = 0;
    const errors: ImportError[] = [];

    for (let i = 0; i < rows.length; i++) {
      const rowNum = i + 2; // +2 weil Header = Zeile 1
      try {
        const mapped = this.mapRow(rows[i], mapping);
        const email = (mapped['email'] ?? '').trim().toLowerCase();

        // Duplikat pruefen
        let existingId: string | null = null;
        if (email) {
          const existing = await this.prisma.contact.findFirst({
            where: {
              tenantId,
              email: { equals: email, mode: 'insensitive' },
            },
          });
          existingId = existing?.id ?? null;
        }

        if (existingId) {
          switch (duplicateStrategy) {
            case DuplicateStrategy.SKIP:
              skipped++;
              continue;
            case DuplicateStrategy.UPDATE:
              await this.prisma.contact.update({
                where: { id: existingId },
                data: {
                  ...this.buildContactData(mapped),
                  updatedBy: userId,
                },
              });
              updated++;
              continue;
            case DuplicateStrategy.MARK:
              mapped['_isDuplicate'] = 'true';
              break;
          }
        }

        const tags = mapped['_isDuplicate'] === 'true' ? ['DUPLIKAT'] : [];
        if (mapped['tags']) {
          tags.push(...mapped['tags'].split(',').map((t: string) => t.trim()));
        }

        await this.prisma.contact.create({
          data: {
            tenantId,
            createdBy: userId,
            firstName: mapped['firstName'] ?? null,
            lastName: mapped['lastName'] ?? null,
            email: mapped['email'] ?? null,
            phone: mapped['phone'] ?? null,
            mobile: mapped['mobile'] ?? null,
            companyName: mapped['companyName'] ?? null,
            position: mapped['position'] ?? null,
            department: mapped['department'] ?? null,
            website: mapped['website'] ?? null,
            linkedinUrl: mapped['linkedinUrl'] ?? null,
            street: mapped['street'] ?? null,
            zip: mapped['zip'] ?? null,
            city: mapped['city'] ?? null,
            state: mapped['state'] ?? null,
            country: mapped['country'] ?? 'DE',
            notes: mapped['notes'] ?? null,
            tags,
            source: 'IMPORT',
            owners: {
              create: { tenantId, userId, role: 'OWNER' },
            },
          },
        });
        created++;
      } catch (err) {
        errors.push({
          row: rowNum,
          field: '',
          value: '',
          message: err instanceof Error ? err.message : 'Unbekannter Fehler',
        });
      }
    }

    return {
      created,
      updated,
      skipped,
      errors: errors.length,
      totalProcessed: rows.length,
      errorDetails: errors.slice(0, 50),
    };
  }

  // --------------------------------------------------------
  // Privat: Unternehmen importieren
  // --------------------------------------------------------

  private async importCompanies(
    tenantId: string,
    userId: string,
    rows: Record<string, string>[],
    mapping: FieldMappingDto[],
    duplicateStrategy: DuplicateStrategy,
  ) {
    let created = 0;
    let updated = 0;
    let skipped = 0;
    const errors: ImportError[] = [];

    for (let i = 0; i < rows.length; i++) {
      const rowNum = i + 2;
      try {
        const mapped = this.mapRow(rows[i], mapping);
        const email = (mapped['email'] ?? '').trim().toLowerCase();

        let existingId: string | null = null;
        if (email) {
          const existing = await this.prisma.company.findFirst({
            where: {
              tenantId,
              email: { equals: email, mode: 'insensitive' },
            },
          });
          existingId = existing?.id ?? null;
        }

        if (existingId) {
          switch (duplicateStrategy) {
            case DuplicateStrategy.SKIP:
              skipped++;
              continue;
            case DuplicateStrategy.UPDATE:
              await this.prisma.company.update({
                where: { id: existingId },
                data: {
                  ...this.buildCompanyData(mapped),
                  updatedBy: userId,
                },
              });
              updated++;
              continue;
            case DuplicateStrategy.MARK:
              mapped['_isDuplicate'] = 'true';
              break;
          }
        }

        const name = mapped['name'] ?? '';
        if (!name) {
          errors.push({
            row: rowNum,
            field: 'name',
            value: '',
            message: 'Unternehmensname ist Pflichtfeld',
          });
          continue;
        }

        const tags = mapped['_isDuplicate'] === 'true' ? ['DUPLIKAT'] : [];
        if (mapped['tags']) {
          tags.push(...mapped['tags'].split(',').map((t: string) => t.trim()));
        }

        await this.prisma.company.create({
          data: {
            tenantId,
            createdBy: userId,
            name,
            email: mapped['email'] ?? null,
            phone: mapped['phone'] ?? null,
            website: mapped['website'] ?? null,
            industry: mapped['industry'] ?? null,
            vatId: mapped['vatId'] ?? null,
            taxId: mapped['taxId'] ?? null,
            tradeRegisterNumber: mapped['tradeRegisterNumber'] ?? null,
            registerCourt: mapped['registerCourt'] ?? null,
            street: mapped['street'] ?? null,
            zip: mapped['zip'] ?? null,
            city: mapped['city'] ?? null,
            state: mapped['state'] ?? null,
            country: mapped['country'] ?? 'DE',
            notes: mapped['notes'] ?? null,
            tags,
            owners: {
              create: { tenantId, userId, role: 'OWNER' },
            },
          },
        });
        created++;
      } catch (err) {
        errors.push({
          row: rowNum,
          field: '',
          value: '',
          message: err instanceof Error ? err.message : 'Unbekannter Fehler',
        });
      }
    }

    return {
      created,
      updated,
      skipped,
      errors: errors.length,
      totalProcessed: rows.length,
      errorDetails: errors.slice(0, 50),
    };
  }

  // --------------------------------------------------------
  // Helfer
  // --------------------------------------------------------

  private mapRow(
    row: Record<string, string>,
    mapping: FieldMappingDto[],
  ): Record<string, string> {
    const result: Record<string, string> = {};
    for (const m of mapping) {
      const value = row[m.sourceColumn];
      if (value !== undefined && value !== '') {
        result[m.targetField] = value;
      }
    }
    return result;
  }

  private buildContactData(mapped: Record<string, string>) {
    return {
      ...(mapped['firstName'] && { firstName: mapped['firstName'] }),
      ...(mapped['lastName'] && { lastName: mapped['lastName'] }),
      ...(mapped['email'] && { email: mapped['email'] }),
      ...(mapped['phone'] && { phone: mapped['phone'] }),
      ...(mapped['mobile'] && { mobile: mapped['mobile'] }),
      ...(mapped['companyName'] && { companyName: mapped['companyName'] }),
      ...(mapped['position'] && { position: mapped['position'] }),
      ...(mapped['department'] && { department: mapped['department'] }),
      ...(mapped['website'] && { website: mapped['website'] }),
      ...(mapped['linkedinUrl'] && { linkedinUrl: mapped['linkedinUrl'] }),
      ...(mapped['street'] && { street: mapped['street'] }),
      ...(mapped['zip'] && { zip: mapped['zip'] }),
      ...(mapped['city'] && { city: mapped['city'] }),
      ...(mapped['state'] && { state: mapped['state'] }),
      ...(mapped['country'] && { country: mapped['country'] }),
      ...(mapped['notes'] && { notes: mapped['notes'] }),
    };
  }

  private buildCompanyData(mapped: Record<string, string>) {
    return {
      ...(mapped['name'] && { name: mapped['name'] }),
      ...(mapped['email'] && { email: mapped['email'] }),
      ...(mapped['phone'] && { phone: mapped['phone'] }),
      ...(mapped['website'] && { website: mapped['website'] }),
      ...(mapped['industry'] && { industry: mapped['industry'] }),
      ...(mapped['vatId'] && { vatId: mapped['vatId'] }),
      ...(mapped['taxId'] && { taxId: mapped['taxId'] }),
      ...(mapped['tradeRegisterNumber'] && { tradeRegisterNumber: mapped['tradeRegisterNumber'] }),
      ...(mapped['registerCourt'] && { registerCourt: mapped['registerCourt'] }),
      ...(mapped['street'] && { street: mapped['street'] }),
      ...(mapped['zip'] && { zip: mapped['zip'] }),
      ...(mapped['city'] && { city: mapped['city'] }),
      ...(mapped['state'] && { state: mapped['state'] }),
      ...(mapped['country'] && { country: mapped['country'] }),
      ...(mapped['notes'] && { notes: mapped['notes'] }),
    };
  }
}
