import { Injectable } from '@nestjs/common';
import { Readable } from 'stream';
import { DataSource } from 'typeorm';

import { FilesService } from '../files/files.service';
import { FileRecord } from '../files/entities/file.entity';
import { MinioService } from '../minio/minio.service';
import { SignaturesRepository } from './signatures.repository';
import { SavedSignature } from './entities/saved-signature.entity';
import { SavedSignatureDto } from './dto/save-signature.dto';

export interface UpsertSignatureInput {
  userId: string;
  signerDisplayName: string;
  signatureImageBase64: string;
}

@Injectable()
export class SignaturesService {
  constructor(
    private readonly signaturesRepository: SignaturesRepository,
    private readonly filesService: FilesService,
    private readonly minioService: MinioService,
    private readonly dataSource: DataSource,
  ) {}

  /** Saved signature for a user, with the image inlined as a data URL (or null). */
  async getForUser(userId: string): Promise<SavedSignatureDto | null> {
    const saved = await this.signaturesRepository.findByUserId(userId);
    if (!saved) return null;
    return {
      signerDisplayName: saved.signerDisplayName,
      signatureImageUrl: await this.inlineImageUrl(saved.signatureFile?.url),
    };
  }

  /** Insert or update the user's reusable signature, storing the image in MinIO. */
  async upsertForUser(input: UpsertSignatureInput): Promise<SavedSignature> {
    const buffer = this.decodeSignatureImage(input.signatureImageBase64);
    const file: Express.Multer.File = {
      fieldname: 'signature',
      originalname: `signature-${input.userId}.png`,
      encoding: '7bit',
      mimetype: 'image/png',
      size: buffer.length,
      buffer,
      stream: Readable.from(buffer),
      destination: '',
      filename: '',
      path: '',
    };

    // Capture the previously referenced file so we can remove it only AFTER the
    // saved_signatures row is repointed — deleting it first would violate the
    // FK constraint (saved_signatures.signature_file_id -> files.id).
    const existing = await this.signaturesRepository.findByUserId(input.userId);
    const previousFileId = existing?.signatureFileId ?? null;

    const fileRecord = await this.filesService.upload({
      resourceId: input.userId,
      resource: 'saved_signatures',
      code: 'signature',
      file,
    });

    const saved = await this.signaturesRepository.upsert({
      userId: input.userId,
      signerDisplayName: input.signerDisplayName,
      signatureFileId: fileRecord.id,
    });

    if (previousFileId && previousFileId !== fileRecord.id) {
      await this.dataSource
        .getRepository(FileRecord)
        .delete({ id: previousFileId });
    }

    return saved;
  }

  private async inlineImageUrl(
    url?: string | null,
  ): Promise<string | null> {
    if (!url) return null;
    if (url.startsWith('data:')) return url;
    try {
      const objectName = this.minioService.getObjectNameFromUrl(url);
      const stream = await this.minioService.getFileStream(objectName);
      const buffer = await this.streamToBuffer(stream);
      return `data:image/png;base64,${buffer.toString('base64')}`;
    } catch {
      return url;
    }
  }

  private decodeSignatureImage(base64: string): Buffer {
    const raw = base64.includes(',') ? base64.split(',')[1]! : base64;
    return Buffer.from(raw, 'base64');
  }

  private streamToBuffer(stream: Readable): Promise<Buffer> {
    return new Promise((resolve, reject) => {
      const chunks: Buffer[] = [];
      stream.on('data', (chunk: Buffer | string) => {
        chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
      });
      stream.on('error', reject);
      stream.on('end', () => resolve(Buffer.concat(chunks)));
    });
  }
}
