const db = require('../db/models');
const ProjectsDBApi = require('../db/api/projects');
const { createEntityService } = require('../factories/service.factory');
const ValidationError = require('./notifications/errors/validation');

// Generate base service from factory
const BaseProjectsService = createEntityService(ProjectsDBApi, {
  entityName: 'Projects',
});

/**
 * Projects service with slug validation and cloning functionality
 * Extends factory-generated service with custom project logic
 */
class ProjectsService extends BaseProjectsService {
  /**
   * Normalize slug to URL-safe format
   */
  static normalizeSlug(value) {
    return (
      String(value || 'project')
        .toLowerCase()
        .trim()
        .replace(/[^a-z0-9]+/g, '-')
        .replace(/^-+|-+$/g, '') || 'project'
    );
  }

  /**
   * Generate unique slug for cloning
   */
  static async generateUniqueSlug(baseSlug, transaction) {
    const normalizedBase = ProjectsService.normalizeSlug(baseSlug);
    let counter = 0;
    let uniqueSlug = null;

    while (uniqueSlug === null) {
      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;
      } else {
        counter += 1;
      }
    }

    return uniqueSlug;
  }

  /**
   * Validate slug uniqueness
   */
  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;
  }

  /**
   * Create project with slug validation
   */
  static async create(data, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      if (data.slug) {
        data.slug = await ProjectsService.validateSlugUniqueness(
          data.slug,
          null,
          transaction,
        );
      }

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

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

  /**
   * Update project with slug validation
   */
  static async update(data, id, currentUser) {
    const transaction = await db.sequelize.transaction();
    try {
      const project = await ProjectsDBApi.findBy({ id }, { transaction });

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

      if (data.slug && data.slug !== project.slug) {
        data.slug = await ProjectsService.validateSlugUniqueness(
          data.slug,
          id,
          transaction,
        );
      }

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

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

  /**
   * Clone project with all assets
   */
  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 },
      );

      // Clone assets and variants
      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;
    }
  }
}

module.exports = ProjectsService;
