const db = require('../db/models');
const ProjectsDBApi = require('../db/api/projects');
const processFile = require('../middlewares/upload');
const ValidationError = require('./notifications/errors/validation');
const csv = require('csv-parser');
const stream = require('stream');

module.exports = class ProjectsService {
  static normalizeSlug(value) {
    return (
      String(value || 'project')
        .toLowerCase()
        .trim()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '') || 'project'
    );
  }

  static async generateUniqueSlug(baseSlug, transaction) {
    const normalizedBase = ProjectsService.normalizeSlug(baseSlug);
    let counter = 0;
    let hasUniqueSlug = false;
    let uniqueSlug = '';

    while (!hasUniqueSlug) {
      const suffix = counter === 0 ? '-copy' : `-copy-${counter + 1}`;
      const candidate = `${normalizedBase}${suffix}`;

      const existing = await db.projects.findOne({
        where: { slug: candidate },
        paranoid: false,
        transaction,
      });

      if (!existing) {
        uniqueSlug = candidate;
        hasUniqueSlug = true;
      } else {
        counter += 1;
      }
    }

    return uniqueSlug;
  }

  /**
   * Validate slug uniqueness before create/update
   * @param {string} slug - Slug to validate
   * @param {string|null} excludeId - Project ID to exclude (for updates)
   * @param {Transaction} transaction - DB transaction
   * @throws {ValidationError} if slug already exists
   * @returns {string} Normalized slug
   */
  static async validateSlugUniqueness(slug, excludeId, transaction) {
    const normalizedSlug = ProjectsService.normalizeSlug(slug);

    const whereClause = { slug: normalizedSlug };
    if (excludeId) {
      whereClause.id = { [db.Sequelize.Op.ne]: excludeId };
    }

    const existing = await db.projects.findOne({
      where: whereClause,
      paranoid: false,
      transaction,
    });

    if (existing) {
      throw new ValidationError('iam.errors.slugAlreadyExists');
    }

    return normalizedSlug;
  }

  static async create(data, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      // Validate slug uniqueness if provided
      if (data.slug) {
        data.slug = await ProjectsService.validateSlugUniqueness(
          data.slug,
          null,
          transaction,
        );
      }

      const createdProject = await ProjectsDBApi.create(data, {
        currentUser,
        transaction,
      });

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

  static async cloneFromProject(sourceProjectId, currentUser) {
    const transaction = await db.sequelize.transaction();

    try {
      const sourceProject = await db.projects.findByPk(sourceProjectId, {
        include: [
          {
            model: db.assets,
            as: 'assets_project',
            required: false,
            include: [
              {
                model: db.asset_variants,
                as: 'asset_variants_asset',
                required: false,
              },
            ],
          },
        ],
        transaction,
      });

      if (!sourceProject) {
        throw new ValidationError('projectsNotFound');
      }

      const uniqueSlug = await ProjectsService.generateUniqueSlug(
        sourceProject.slug,
        transaction,
      );

      const clonedProject = await ProjectsDBApi.create(
        {
          name: `${sourceProject.name} (Copy)`,
          slug: uniqueSlug,
          description: sourceProject.description,
          logo_url: sourceProject.logo_url,
          favicon_url: sourceProject.favicon_url,
          og_image_url: sourceProject.og_image_url,
        },
        {
          currentUser,
          transaction,
        },
      );

      for (const sourceAsset of sourceProject.assets_project || []) {
        const clonedAsset = await db.assets.create(
          {
            name: sourceAsset.name,
            asset_type: sourceAsset.asset_type,
            type: sourceAsset.type || 'general',
            cdn_url: sourceAsset.cdn_url,
            storage_key: sourceAsset.storage_key,
            mime_type: sourceAsset.mime_type,
            size_mb: sourceAsset.size_mb,
            width_px: sourceAsset.width_px,
            height_px: sourceAsset.height_px,
            duration_sec: sourceAsset.duration_sec,
            checksum: sourceAsset.checksum,
            is_public: sourceAsset.is_public,
            projectId: clonedProject.id,
            createdById: currentUser.id,
            updatedById: currentUser.id,
          },
          { transaction },
        );

        for (const sourceVariant of sourceAsset.asset_variants_asset || []) {
          await db.asset_variants.create(
            {
              variant_type: sourceVariant.variant_type,
              cdn_url: sourceVariant.cdn_url,
              width_px: sourceVariant.width_px,
              height_px: sourceVariant.height_px,
              size_mb: sourceVariant.size_mb,
              assetId: clonedAsset.id,
              createdById: currentUser.id,
              updatedById: currentUser.id,
            },
            { transaction },
          );
        }
      }

      await transaction.commit();
      return clonedProject;
    } catch (error) {
      await transaction.rollback();
      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')); // convert Buffer to Stream

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

      await ProjectsDBApi.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 {
      let projects = await ProjectsDBApi.findBy({ id }, { transaction });

      if (!projects) {
        throw new ValidationError('projectsNotFound');
      }

      // Validate slug uniqueness if slug is being changed
      if (data.slug && data.slug !== projects.slug) {
        data.slug = await ProjectsService.validateSlugUniqueness(
          data.slug,
          id,
          transaction,
        );
      }

      const updatedProjects = await ProjectsDBApi.update(id, data, {
        currentUser,
        transaction,
      });

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

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

    try {
      await ProjectsDBApi.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 ProjectsDBApi.remove(id, {
        currentUser,
        transaction,
      });

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