import { Injectable, inject } from '@angular/core';
import { RealtimeSessionFacade } from '../../../../core/realtime';
import { AttachmentStorageService } from '../../infrastructure/services/attachment-storage.service';
import { FILE_CHUNK_SIZE_BYTES } from '../../domain/constants/attachment-transfer.constants';
import { FileChunkEvent } from '../../domain/models/attachment-transfer.model';
import {
  arrayBufferToBase64,
  decodeBase64,
  iterateBlobChunks
} from '../../../../shared-kernel';

@Injectable({ providedIn: 'root' })
export class AttachmentTransferTransportService {
  private readonly webrtc = inject(RealtimeSessionFacade);
  private readonly attachmentStorage = inject(AttachmentStorageService);

  decodeBase64(base64: string): Uint8Array {
    return decodeBase64(base64);
  }

  async streamFileToPeer(
    targetPeerId: string,
    messageId: string,
    fileId: string,
    file: File,
    isCancelled: () => boolean
  ): Promise<void> {
    for await (const chunk of iterateBlobChunks(file, FILE_CHUNK_SIZE_BYTES)) {
      if (isCancelled())
        break;

      const fileChunkEvent: FileChunkEvent = {
        type: 'file-chunk',
        messageId,
        fileId,
        index: chunk.index,
        total: chunk.total,
        data: chunk.base64
      };

      await this.webrtc.sendToPeerBuffered(targetPeerId, fileChunkEvent);
    }
  }

  async streamFileFromDiskToPeer(
    targetPeerId: string,
    messageId: string,
    fileId: string,
    diskPath: string,
    isCancelled: () => boolean
  ): Promise<void> {
    if (this.attachmentStorage.canReadFileChunks()) {
      await this.streamFileFromDiskChunksToPeer(targetPeerId, messageId, fileId, diskPath, isCancelled);
      return;
    }

    const base64Full = await this.attachmentStorage.readFile(diskPath);

    if (!base64Full)
      return;

    const fileBytes = decodeBase64(base64Full);
    const totalChunks = Math.ceil(fileBytes.byteLength / FILE_CHUNK_SIZE_BYTES);

    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
      if (isCancelled())
        break;

      const start = chunkIndex * FILE_CHUNK_SIZE_BYTES;
      const end = Math.min(fileBytes.byteLength, start + FILE_CHUNK_SIZE_BYTES);
      const slice = fileBytes.subarray(start, end);
      const sliceBuffer = (slice.buffer as ArrayBuffer).slice(
        slice.byteOffset,
        slice.byteOffset + slice.byteLength
      );
      const base64Chunk = arrayBufferToBase64(sliceBuffer);
      const fileChunkEvent: FileChunkEvent = {
        type: 'file-chunk',
        messageId,
        fileId,
        index: chunkIndex,
        total: totalChunks,
        data: base64Chunk
      };

      await this.webrtc.sendToPeerBuffered(targetPeerId, fileChunkEvent);
    }
  }

  private async streamFileFromDiskChunksToPeer(
    targetPeerId: string,
    messageId: string,
    fileId: string,
    diskPath: string,
    isCancelled: () => boolean
  ): Promise<void> {
    const fileSize = await this.attachmentStorage.getFileSize(diskPath);

    if (fileSize === null)
      return;

    const totalChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE_BYTES);

    for (let chunkIndex = 0; chunkIndex < totalChunks; chunkIndex++) {
      if (isCancelled())
        break;

      const start = chunkIndex * FILE_CHUNK_SIZE_BYTES;
      const end = Math.min(fileSize, start + FILE_CHUNK_SIZE_BYTES);
      const base64Chunk = await this.attachmentStorage.readFileChunk(diskPath, start, end);

      if (base64Chunk === null)
        return;

      const fileChunkEvent: FileChunkEvent = {
        type: 'file-chunk',
        messageId,
        fileId,
        index: chunkIndex,
        total: totalChunks,
        data: base64Chunk
      };

      await this.webrtc.sendToPeerBuffered(targetPeerId, fileChunkEvent);
    }
  }
}
