import {
  Injectable,
  NotFoundException,
  BadRequestException,
} from '@nestjs/common';
import { CrmPrismaService } from '../prisma/crm-prisma.service';
import { CreateContractDto } from './dto/create-contract.dto';
import { UpdateContractDto } from './dto/update-contract.dto';
import { QueryContractsDto } from './dto/query-contracts.dto';
import { Prisma } from '.prisma/crm-client';
import * as fs from 'fs';
import * as path from 'path';
import { randomUUID } from 'crypto';

const UPLOAD_BASE = '/app/uploads/contracts';
const MAX_FILES_PER_CONTRACT = 10;
const ALLOWED_MIME_TYPES = [
  'application/pdf',
  'application/msword',
  'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
  'application/vnd.ms-excel',
  'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
];

@Injectable()
export class ContractsService {
  constructor(private readonly prisma: CrmPrismaService) {}

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

    return this.prisma.contract.create({
      data: {
        tenantId,
        companyId,
        title: dto.title,
        status: dto.status ?? 'DRAFT',
        startDate: dto.startDate ? new Date(dto.startDate) : undefined,
        endDate: dto.endDate ? new Date(dto.endDate) : undefined,
        value: dto.value,
        currency: dto.currency ?? 'EUR',
        notes: dto.notes,
        createdBy: userId,
      },
      include: {
        company: { select: { id: true, name: true } },
      },
    });
  }

  async findAll(tenantId: string, companyId: string, query: QueryContractsDto) {
    const page = query.page ?? 1;
    const pageSize = query.pageSize ?? 25;

    const where: Prisma.ContractWhereInput = { tenantId, companyId };

    if (query.status) {
      where.status = query.status;
    }

    if (query.search) {
      where.title = { contains: query.search, mode: 'insensitive' };
    }

    const allowedSortFields = [
      'createdAt',
      'updatedAt',
      'title',
      'startDate',
      'endDate',
      'value',
      'status',
    ];
    const sortField = allowedSortFields.includes(query.sort ?? '')
      ? (query.sort as string)
      : 'createdAt';

    const [data, total] = await Promise.all([
      this.prisma.contract.findMany({
        where,
        skip: (page - 1) * pageSize,
        take: pageSize,
        orderBy: { [sortField]: query.order ?? 'desc' },
        include: {
          company: { select: { id: true, name: true } },
        },
      }),
      this.prisma.contract.count({ where }),
    ]);

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

  async findOne(tenantId: string, companyId: string, id: string) {
    const contract = await this.prisma.contract.findFirst({
      where: { id, tenantId, companyId },
      include: {
        company: { select: { id: true, name: true } },
      },
    });

    if (!contract) {
      throw new NotFoundException('Vertrag nicht gefunden');
    }

    return contract;
  }

  async update(
    tenantId: string,
    companyId: string,
    id: string,
    dto: UpdateContractDto,
  ) {
    await this.findOne(tenantId, companyId, id);

    const data: Prisma.ContractUpdateInput = {};

    if (dto.title !== undefined) data.title = dto.title;
    if (dto.status !== undefined) data.status = dto.status;
    if (dto.startDate !== undefined)
      data.startDate = dto.startDate ? new Date(dto.startDate) : null;
    if (dto.endDate !== undefined)
      data.endDate = dto.endDate ? new Date(dto.endDate) : null;
    if (dto.value !== undefined) data.value = dto.value;
    if (dto.currency !== undefined) data.currency = dto.currency;
    if (dto.notes !== undefined) data.notes = dto.notes;

    return this.prisma.contract.update({
      where: { id },
      data,
      include: {
        company: { select: { id: true, name: true } },
      },
    });
  }

  async remove(tenantId: string, companyId: string, id: string) {
    const contract = await this.findOne(tenantId, companyId, id);

    // Dateien auf Disk loeschen bevor DB-Cascade die Records entfernt
    const files = await this.prisma.contractFile.findMany({
      where: { contractId: id, tenantId },
    });
    for (const file of files) {
      const absPath = path.resolve(file.storagePath);
      try {
        fs.unlinkSync(absPath);
      } catch {
        // Datei bereits geloescht oder nicht vorhanden — ignorieren
      }
    }

    await this.prisma.contract.delete({ where: { id } });
    return contract;
  }

  // --- Contract Files ---

  async uploadFile(
    tenantId: string,
    companyId: string,
    contractId: string,
    userId: string,
    file: Express.Multer.File,
  ) {
    // Vertrag validieren
    await this.findOne(tenantId, companyId, contractId);

    // MIME-Typ pruefen
    if (!ALLOWED_MIME_TYPES.includes(file.mimetype)) {
      throw new BadRequestException(
        `Dateityp '${file.mimetype}' nicht erlaubt. Erlaubt: PDF, Word, Excel`,
      );
    }

    // Max-Dateien pruefen
    const fileCount = await this.prisma.contractFile.count({
      where: { contractId, tenantId },
    });
    if (fileCount >= MAX_FILES_PER_CONTRACT) {
      throw new BadRequestException(
        `Maximal ${MAX_FILES_PER_CONTRACT} Dateien pro Vertrag erlaubt`,
      );
    }

    // Verzeichnis erstellen
    const dir = path.join(UPLOAD_BASE, tenantId, contractId);
    fs.mkdirSync(dir, { recursive: true });

    // Datei speichern (UUID-Prefix gegen Kollisionen + Path-Traversal)
    const fileUuid = randomUUID();
    const safeName = path.basename(file.originalname);
    const diskName = `${fileUuid}-${safeName}`;
    const storagePath = path.join(dir, diskName);

    fs.writeFileSync(storagePath, file.buffer);

    // DB-Record anlegen
    return this.prisma.contractFile.create({
      data: {
        tenantId,
        contractId,
        originalName: file.originalname,
        storagePath,
        mimeType: file.mimetype,
        size: file.size,
        uploadedBy: userId,
      },
      select: {
        id: true,
        contractId: true,
        originalName: true,
        mimeType: true,
        size: true,
        uploadedBy: true,
        createdAt: true,
      },
    });
  }

  async listFiles(tenantId: string, companyId: string, contractId: string) {
    // Vertrag validieren
    await this.findOne(tenantId, companyId, contractId);

    return this.prisma.contractFile.findMany({
      where: { contractId, tenantId },
      select: {
        id: true,
        contractId: true,
        originalName: true,
        mimeType: true,
        size: true,
        uploadedBy: true,
        createdAt: true,
      },
      orderBy: { createdAt: 'desc' },
    });
  }

  async getFile(
    tenantId: string,
    companyId: string,
    contractId: string,
    fileId: string,
  ) {
    // Vertrag validieren
    await this.findOne(tenantId, companyId, contractId);

    const file = await this.prisma.contractFile.findFirst({
      where: { id: fileId, contractId, tenantId },
    });

    if (!file) {
      throw new NotFoundException('Datei nicht gefunden');
    }

    const absPath = path.resolve(file.storagePath);
    if (!fs.existsSync(absPath)) {
      throw new NotFoundException('Datei auf Disk nicht gefunden');
    }

    return { file, absPath };
  }

  async deleteFile(
    tenantId: string,
    companyId: string,
    contractId: string,
    fileId: string,
  ) {
    // Vertrag validieren
    await this.findOne(tenantId, companyId, contractId);

    const file = await this.prisma.contractFile.findFirst({
      where: { id: fileId, contractId, tenantId },
    });

    if (!file) {
      throw new NotFoundException('Datei nicht gefunden');
    }

    // DB-Record loeschen
    await this.prisma.contractFile.delete({ where: { id: fileId } });

    // Datei auf Disk loeschen (kein Fehler wenn nicht vorhanden)
    try {
      fs.unlinkSync(path.resolve(file.storagePath));
    } catch {
      // Datei bereits geloescht — ignorieren
    }

    return { id: fileId, deleted: true };
  }
}
