import { PassThrough } from 'stream';
import csv from 'csv-parser';
import db from '@/db/models';
import DbApi from '@/db/api/documents';
import ValidationError from '@/shared/errors/validation';
import type { CurrentUser } from '@/db/api/types';
import type { Documents } from '@/db/models/documents';

type CreateData = Parameters<typeof DbApi.create>[0];
type UpdateData = Parameters<typeof DbApi.update>[1];
type ListFilter = Parameters<typeof DbApi.findAll>[0];
type BulkRow = Parameters<typeof DbApi.bulkImport>[0][number];

/** The document DTO exposed to the frontend — only the contract fields, without
 * importHash/deletedAt or eager-loaded relations. */
export function toDocumentDto(record: Documents) {
  const plain = record.get({ plain: true });
  return {
    id: plain.id,
    entity_type: plain.entity_type,
    entity_reference: plain.entity_reference,
    name: plain.name,
    category: plain.category,
    uploaded_at: plain.uploaded_at,
    notes: plain.notes,
    organizationId: plain.organizationId,
    campusId: plain.campusId,
    createdById: plain.createdById,
    updatedById: plain.updatedById,
    createdAt: plain.createdAt,
    updatedAt: plain.updatedAt,
  };
}

/** Parses an uploaded CSV buffer into bulk-import rows. */
function parseCsvRows(fileBuffer: Buffer): Promise<BulkRow[]> {
  const bufferStream = new PassThrough();
  const results: BulkRow[] = [];
  bufferStream.end(Buffer.from(fileBuffer));

  return new Promise((resolve, reject) => {
    bufferStream
      .pipe(csv())
      .on('data', (row: BulkRow) => results.push(row))
      .on('end', () => resolve(results))
      .on('error', reject);
  });
}

class DocumentsService {
  static async create(data: CreateData, currentUser?: CurrentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      const created = await DbApi.create(data, { currentUser, transaction });
      await transaction.commit();
      return toDocumentDto(created);
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static async bulkImport(fileBuffer: Buffer, currentUser?: CurrentUser) {
    const rows = await parseCsvRows(fileBuffer);
    const transaction = await db.sequelize.transaction();
    try {
      await DbApi.bulkImport(rows, {
        transaction,
        ignoreDuplicates: true,
        validate: true,
        currentUser,
      });
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static async update(data: UpdateData, id: string, currentUser?: CurrentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      const updated = await DbApi.update(id, data, { currentUser, transaction });

      if (!updated) {
        throw new ValidationError('documentsNotFound');
      }

      await transaction.commit();
      return toDocumentDto(updated);
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static async deleteByIds(ids: string[], currentUser?: CurrentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      await DbApi.deleteByIds(ids, { currentUser, transaction });
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static async remove(id: string, currentUser?: CurrentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      await DbApi.remove(id, { currentUser, transaction });
      await transaction.commit();
    } catch (error) {
      await transaction.rollback();
      throw error;
    }
  }

  static list(
    filter: ListFilter,
    globalAccess: boolean,
    currentUser?: CurrentUser,
  ) {
    return DbApi.findAll(filter, globalAccess, { currentUser });
  }

  static count(
    filter: ListFilter,
    globalAccess: boolean,
    currentUser?: CurrentUser,
  ) {
    return DbApi.findAll(filter, globalAccess, { countOnly: true, currentUser });
  }

  static autocomplete(
    query: string | undefined,
    limit: number | undefined,
    offset: number | undefined,
    globalAccess: boolean,
    organizationId?: string,
  ) {
    return DbApi.findAllAutocomplete(
      query,
      limit,
      offset,
      globalAccess,
      organizationId,
    );
  }

  static findById(id: string) {
    return DbApi.findBy({ id });
  }
}

export default DocumentsService;
