const db = require('../db/models');
const Sync_jobsDBApi = require('../db/api/sync_jobs');
const processFile = require('../middlewares/upload');
const ValidationError = require('./notifications/errors/validation');
const OsuCatalogSyncService = require('./osuCatalogSync');
const csv = require('csv-parser');
const stream = require('stream');

async function createStoredJob(data, currentUser) {
  const transaction = await db.sequelize.transaction();

  try {
    const syncJob = await Sync_jobsDBApi.create(
      data,
      {
        currentUser,
        transaction,
      },
    );

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

async function markJobFailed(jobId, currentUser, error) {
  try {
    await db.sync_jobs.update(
      {
        status: 'failed',
        finished_at: new Date(),
        error_count: 1,
        error_summary: error.message,
        updatedById: currentUser?.id || null,
      },
      {
        where: { id: jobId },
      },
    );
  } catch (updateError) {
    console.error('Failed to update sync job failure state:', updateError);
  }
}

module.exports = class Sync_jobsService {
  static async create(data, currentUser) {
    const shouldRunJob =
      OsuCatalogSyncService.isRunnableJobType(data?.job_type) &&
      (!data?.status || ['queued', 'running'].includes(data.status));

    const initialPayload = shouldRunJob
      ? {
          ...data,
          status: 'running',
          started_at: new Date(),
          finished_at: null,
          processed_count: 0,
          inserted_count: 0,
          updated_count: 0,
          skipped_count: 0,
          error_count: 0,
          error_summary: null,
          triggered_by: currentUser?.id || null,
        }
      : data;

    const syncJob = await createStoredJob(initialPayload, currentUser);

    if (!shouldRunJob) {
      return syncJob;
    }

    try {
      const result = await OsuCatalogSyncService.run(syncJob, currentUser);

      await db.sync_jobs.update(
        {
          status: 'succeeded',
          finished_at: new Date(),
          processed_count: result.processedCount,
          inserted_count: result.insertedCount,
          updated_count: result.updatedCount,
          skipped_count: result.skippedCount,
          error_count: result.errorCount || 0,
          error_summary: result.summary,
          updatedById: currentUser.id,
        },
        {
          where: { id: syncJob.id },
        },
      );

      return db.sync_jobs.findByPk(syncJob.id);
    } catch (error) {
      console.error('Sync job failed:', error);
      await markJobFailed(syncJob.id, currentUser, error);
      throw error;
    }
  }

  static async bulkImport(req, res) {
    const transaction = await db.sequelize.transaction();

    try {
      await processFile(req, res);
      const bufferStream = new stream.PassThrough();
      const results = [];

      await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8'));

      await new Promise((resolve, reject) => {
        bufferStream
          .pipe(csv())
          .on('data', (row) => results.push(row))
          .on('end', () => {
            console.log('CSV results', results);
            resolve();
          })
          .on('error', (error) => reject(error));
      });

      await Sync_jobsDBApi.bulkImport(results, {
        transaction,
        ignoreDuplicates: true,
        validate: true,
        currentUser: req.currentUser,
      });

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

  static async update(data, id, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      const sync_jobs = await Sync_jobsDBApi.findBy(
        { id },
        { transaction },
      );

      if (!sync_jobs) {
        throw new ValidationError('sync_jobsNotFound');
      }

      const updatedSync_jobs = await Sync_jobsDBApi.update(
        id,
        data,
        {
          currentUser,
          transaction,
        },
      );

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

  static async deleteByIds(ids, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      await Sync_jobsDBApi.deleteByIds(ids, {
        currentUser,
        transaction,
      });

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

  static async remove(id, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      await Sync_jobsDBApi.remove(
        id,
        {
          currentUser,
          transaction,
        },
      );

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