import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';

import {
  CreateDjiboutiIncidentDto,
  CreateEmptyContainerReturnDto,
  ImportOperationActionDto,
  RecordDeclarationDto,
  AssignCustomsRiskDto,
  UpdateEmptyContainerReturnStatusDto,
  UploadImportCustomsDocumentDto,
} from './dto/import-operations.dto';
import {
  DjiboutiIncident,
  type DjiboutiIncidentType,
} from './entities/djibouti-incident.entity';
import { EmptyContainerReturn } from './entities/empty-container-return.entity';
import {
  ImportCustomsFinalization,
  type ImportCustomsDocumentType,
} from './entities/import-customs-finalization.entity';

const DAMAGE_INCIDENTS: DjiboutiIncidentType[] = [
  'SEAL_BROKEN',
  'CONTAINER_OPENED',
  'CONTAINER_DAMAGED',
  'FLUID_LEAKING',
];

@Injectable()
export class ImportOperationsService {
  constructor(
    @InjectRepository(DjiboutiIncident)
    private readonly incidents: Repository<DjiboutiIncident>,
    @InjectRepository(ImportCustomsFinalization)
    private readonly customs: Repository<ImportCustomsFinalization>,
    @InjectRepository(EmptyContainerReturn)
    private readonly emptyReturns: Repository<EmptyContainerReturn>,
  ) {}

  listIncidents(bookingId?: string) {
    return this.incidents.find({
      where: bookingId ? { bookingId } : {},
      order: { reportedAt: 'DESC', createdAt: 'DESC' } as never,
    });
  }

  async createIncident(dto: CreateDjiboutiIncidentDto) {
    const photos = dto.photos ?? [];
    if (DAMAGE_INCIDENTS.includes(dto.incidentType) && photos.length === 0) {
      throw new BadRequestException('Photos are required for damage-related Djibouti incidents');
    }

    const incident = await this.incidents.save(
      this.incidents.create({
        bookingId: dto.bookingId,
        containerNumber: dto.containerNumber ?? null,
        cargoId: dto.cargoId ?? null,
        facility: dto.facility ?? null,
        station: dto.station ?? null,
        incidentType: dto.incidentType,
        description: dto.description,
        photos,
        reportedBy: dto.reportedBy ?? null,
        reportedAt: dto.reportedAt ? new Date(dto.reportedAt) : new Date(),
      }),
    );

    console.log(
      `[NOTIFY] Djibouti incident ${incident.incidentType} for booking ${incident.bookingId}; notify Global Logistics Ethiopia and customer.`,
    );
    console.log(
      `[MOVEMENT] Attach incident ${incident.id} to booking ${incident.bookingId} movement history.`,
    );
    return incident;
  }

  async getCustoms(bookingId: string) {
    return this.getOrCreateCustoms(bookingId);
  }

  async uploadCustomsDocument(bookingId: string, dto: UploadImportCustomsDocumentDto) {
    const row = await this.getOrCreateCustoms(bookingId);
    const documents = { ...(row.documents ?? {}), [dto.documentType]: dto.fileId };
    await this.customs.update(row.id, {
      documents,
      performedBy: dto.performedBy ?? row.performedBy ?? null,
    });
    return this.getCustoms(bookingId);
  }

  async recordDeclaration(bookingId: string, dto: RecordDeclarationDto) {
    const row = await this.getOrCreateCustoms(bookingId);
    await this.customs.update(row.id, {
      declarationSerialNumber: dto.declarationSerialNumber,
      performedBy: dto.performedBy ?? row.performedBy ?? null,
    });
    return this.getCustoms(bookingId);
  }

  async notifyDutiesTaxes(bookingId: string, dto: ImportOperationActionDto = {}) {
    const row = await this.getOrCreateCustoms(bookingId);
    await this.customs.update(row.id, {
      dutiesTaxesNotifiedAt: row.dutiesTaxesNotifiedAt ?? new Date(),
      performedBy: dto.performedBy ?? row.performedBy ?? null,
      notes: dto.notes ?? row.notes ?? null,
    });
    console.log(`[NOTIFY] Duties and taxes notification sent for booking ${bookingId}.`);
    return this.getCustoms(bookingId);
  }

  async markDutiesTaxesPaid(bookingId: string, dto: ImportOperationActionDto = {}) {
    const row = await this.getOrCreateCustoms(bookingId);
    this.assertDocument(row, 'CUSTOMER_PAYMENT_SLIP', 'Customer payment slip is required before marking duties and taxes paid');
    await this.customs.update(row.id, {
      dutiesTaxesPaidAt: row.dutiesTaxesPaidAt ?? new Date(),
      performedBy: dto.performedBy ?? row.performedBy ?? null,
      notes: dto.notes ?? row.notes ?? null,
    });
    return this.getCustoms(bookingId);
  }

  async assignRisk(bookingId: string, dto: AssignCustomsRiskDto) {
    const row = await this.getOrCreateCustoms(bookingId);
    await this.customs.update(row.id, {
      customsRisk: dto.risk,
      performedBy: dto.performedBy ?? row.performedBy ?? null,
    });
    console.log(`[NOTIFY] Customs risk ${dto.risk} assigned for booking ${bookingId}; notify customer.`);
    return this.getCustoms(bookingId);
  }

  async markReleasePermitted(bookingId: string, dto: ImportOperationActionDto = {}) {
    const row = await this.getOrCreateCustoms(bookingId);
    this.assertReleaseReady(row);
    await this.customs.update(row.id, {
      importReleasePermittedAt: row.importReleasePermittedAt ?? new Date(),
      completedAt: row.completedAt ?? new Date(),
      performedBy: dto.performedBy ?? row.performedBy ?? null,
      notes: dto.notes ?? row.notes ?? null,
    });
    console.log(`[NOTIFY] Import release permitted for booking ${bookingId}; notify customer.`);
    return this.getCustoms(bookingId);
  }

  listEmptyReturns() {
    return this.emptyReturns.find({ order: { createdAt: 'DESC' } as never });
  }

  async createEmptyReturn(dto: CreateEmptyContainerReturnDto) {
    return this.emptyReturns.save(
      this.emptyReturns.create({
        containerNumber: dto.containerNumber,
        bookingId: dto.bookingId ?? null,
        customerId: dto.customerId ?? null,
        returnDate: dto.returnDate ? new Date(dto.returnDate) : new Date(),
        facility: dto.facility ?? null,
        yard: dto.yard ?? null,
        zone: dto.zone ?? null,
        condition: dto.condition ?? null,
        handoverNote: dto.handoverNote ?? null,
        performedBy: dto.performedBy ?? null,
      }),
    );
  }

  async updateEmptyReturnStatus(id: string, dto: UpdateEmptyContainerReturnStatusDto) {
    const row = await this.emptyReturns.findOne({ where: { id } });
    if (!row) {
      throw new NotFoundException(`Empty container return ${id} not found`);
    }
    await this.emptyReturns.update(id, {
      status: dto.status,
      wagonAllocationReference: dto.wagonAllocationReference ?? row.wagonAllocationReference ?? null,
      handoverNote: dto.handoverNote ?? row.handoverNote ?? null,
      performedBy: dto.performedBy ?? row.performedBy ?? null,
    });
    return this.emptyReturns.findOneOrFail({ where: { id } });
  }

  private async getOrCreateCustoms(bookingId: string) {
    const existing = await this.customs.findOne({ where: { bookingId } });
    if (existing) return existing;
    return this.customs.save(this.customs.create({ bookingId, documents: {} }));
  }

  private assertDocument(
    row: ImportCustomsFinalization,
    type: ImportCustomsDocumentType,
    message: string,
  ) {
    if (!row.documents?.[type]) {
      throw new BadRequestException(message);
    }
  }

  private assertReleaseReady(row: ImportCustomsFinalization) {
    this.assertDocument(row, 'T1_CLOSURE_PROOF', 'T1 closure proof is required before import release');
    this.assertDocument(row, 'IMPORT_RELEASE_PERMIT', 'Import release permit upload is required before release is permitted');
    if (!row.declarationSerialNumber?.trim()) {
      throw new BadRequestException('Declaration serial number is required before import release');
    }
    if (!row.customsRisk) {
      throw new BadRequestException('Customs risk must be assigned before import release');
    }
    if (!row.dutiesTaxesPaidAt) {
      throw new BadRequestException('Duties and taxes must be paid before import release');
    }
  }
}
