const db = require('../db/models');

const EVENT_STATUS = {
  QUEUED: 'queued',
  RUNNING: 'running',
  SUCCESS: 'success',
  FAILED: 'failed',
};

const ENVIRONMENT = {
  DEV: 'dev',
  STAGE: 'stage',
  PRODUCTION: 'production',
};

const sanitizeRecordForClone = (modelInstance) => {
  const data = modelInstance.toJSON();
  delete data.id;
  delete data.createdAt;
  delete data.updatedAt;
  delete data.deletedAt;
  delete data.deletedBy;
  delete data.importHash;

  // Ensure JSON fields are objects, not strings (avoid double-encoding)
  if (data.ui_schema_json && typeof data.ui_schema_json === 'string') {
    try {
      data.ui_schema_json = JSON.parse(data.ui_schema_json);
    } catch {
      // Keep as-is if parsing fails
    }
  }

  return data;
};

module.exports = class PublishService {
  static async withProjectPublishLock(projectId, callback) {
    return db.sequelize.transaction(async (transaction) => {
      const project = await db.projects.findByPk(projectId, {
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (!project) {
        const error = new Error('Project not found');
        error.code = 404;
        throw error;
      }

      const runningEvent = await db.publish_events.findOne({
        where: {
          projectId,
          status: EVENT_STATUS.RUNNING,
        },
        order: [['createdAt', 'DESC']],
        transaction,
        lock: transaction.LOCK.UPDATE,
      });

      if (runningEvent) {
        const error = new Error('Publish is already running for this project');
        error.code = 400;
        throw error;
      }

      return callback(transaction);
    });
  }

  static async publishToProduction(projectId, currentUser, title, description) {
    if (!projectId) {
      const error = new Error('projectId is required');
      error.code = 400;
      throw error;
    }

    const eventTitle = typeof title === 'string' ? title.trim() : '';
    const eventDescription =
      typeof description === 'string' ? description.trim() : '';

    if (!eventTitle) {
      const error = new Error('title is required');
      error.code = 400;
      throw error;
    }

    if (!eventDescription) {
      const error = new Error('description is required');
      error.code = 400;
      throw error;
    }

    const publishEvent = await db.publish_events.create({
      projectId,
      userId: currentUser?.id || null,
      title: eventTitle,
      description: eventDescription,
      from_environment: ENVIRONMENT.STAGE,
      to_environment: ENVIRONMENT.PRODUCTION,
      status: EVENT_STATUS.QUEUED,
      createdById: currentUser?.id || null,
      updatedById: currentUser?.id || null,
    });

    try {
      const summary = await this.withProjectPublishLock(
        projectId,
        async (transaction) => {
          await publishEvent.update(
            {
              started_at: new Date(),
              status: EVENT_STATUS.RUNNING,
              error_message: null,
              updatedById: currentUser?.id || null,
            },
            { transaction },
          );

          return this.copyStageToProduction(
            projectId,
            currentUser,
            transaction,
          );
        },
      );

      await publishEvent.update({
        status: EVENT_STATUS.SUCCESS,
        finished_at: new Date(),
        pages_copied: summary.pages_copied,
        audios_copied: summary.audios_copied,
        error_message: null,
        updatedById: currentUser?.id || null,
      });

      return {
        success: true,
        publishEventId: publishEvent.id,
        summary,
      };
    } catch (error) {
      await publishEvent.update({
        status: EVENT_STATUS.FAILED,
        finished_at: new Date(),
        error_message: error.message,
        updatedById: currentUser?.id || null,
      });
      throw error;
    }
  }

  /**
   * Save dev content to stage environment
   * This is the first step in the publishing workflow: dev -> stage -> production
   */
  static async saveToStage(projectId, currentUser) {
    if (!projectId) {
      const error = new Error('projectId is required');
      error.code = 400;
      throw error;
    }

    const publishEvent = await db.publish_events.create({
      projectId,
      userId: currentUser?.id || null,
      title: 'Save to Stage',
      description: 'Copy dev content to stage environment',
      from_environment: ENVIRONMENT.DEV,
      to_environment: ENVIRONMENT.STAGE,
      status: EVENT_STATUS.QUEUED,
      createdById: currentUser?.id || null,
      updatedById: currentUser?.id || null,
    });

    try {
      const summary = await this.withProjectPublishLock(
        projectId,
        async (transaction) => {
          await publishEvent.update(
            {
              started_at: new Date(),
              status: EVENT_STATUS.RUNNING,
              error_message: null,
              updatedById: currentUser?.id || null,
            },
            { transaction },
          );

          return this.copyDevToStage(projectId, currentUser, transaction);
        },
      );

      await publishEvent.update({
        status: EVENT_STATUS.SUCCESS,
        finished_at: new Date(),
        pages_copied: summary.pages_copied,
        audios_copied: summary.audios_copied,
        error_message: null,
        updatedById: currentUser?.id || null,
      });

      return {
        success: true,
        publishEventId: publishEvent.id,
        summary,
      };
    } catch (error) {
      await publishEvent.update({
        status: EVENT_STATUS.FAILED,
        finished_at: new Date(),
        error_message: error.message,
        updatedById: currentUser?.id || null,
      });
      throw error;
    }
  }

  /**
   * Copy dev content to stage environment
   */
  static async copyDevToStage(projectId, currentUser, transaction) {
    return this.copyEnvironment(
      projectId,
      ENVIRONMENT.DEV,
      ENVIRONMENT.STAGE,
      currentUser,
      transaction,
    );
  }

  static async copyStageToProduction(projectId, currentUser, transaction) {
    return this.copyEnvironment(
      projectId,
      ENVIRONMENT.STAGE,
      ENVIRONMENT.PRODUCTION,
      currentUser,
      transaction,
    );
  }

  /**
   * Generic method to copy content from one environment to another.
   * Used for both dev->stage and stage->production flows.
   *
   * SIMPLIFIED: Now uses targetPageSlug for navigation which is consistent across environments.
   * No ID remapping needed since slugs are the same in all environments.
   *
   * @param {string} projectId - Project ID
   * @param {string} fromEnv - Source environment (dev, stage)
   * @param {string} toEnv - Target environment (stage, production)
   * @param {object} currentUser - Current user for audit fields
   * @param {object} transaction - Sequelize transaction
   * @returns {object} Summary of copied items
   */
  static async copyEnvironment(
    projectId,
    fromEnv,
    toEnv,
    currentUser,
    transaction,
  ) {
    // Get source content
    const [sourcePages, sourceAudioTracks] = await Promise.all([
      db.tour_pages.findAll({
        where: { projectId, environment: fromEnv },
        transaction,
      }),
      db.project_audio_tracks.findAll({
        where: { projectId, environment: fromEnv },
        transaction,
      }),
    ]);

    // Clean up target environment (hard delete - paranoid models need force: true)
    await Promise.all([
      db.tour_pages.destroy({
        where: { projectId, environment: toEnv },
        transaction,
        force: true,
      }),
      db.project_audio_tracks.destroy({
        where: { projectId, environment: toEnv },
        transaction,
        force: true,
      }),
    ]);

    const actorId = currentUser?.id || null;

    // Create target pages - ui_schema_json uses targetPageSlug which is consistent across environments
    const targetPagesPayload = sourcePages.map((sourcePage) => {
      const data = sanitizeRecordForClone(sourcePage);
      return {
        ...data,
        projectId,
        environment: toEnv,
        source_key: sourcePage.id,
        createdById: actorId,
        updatedById: actorId,
      };
    });

    // Create target audio tracks
    const targetAudioPayload = sourceAudioTracks.map((sourceAudio) => {
      const data = sanitizeRecordForClone(sourceAudio);
      return {
        ...data,
        projectId,
        environment: toEnv,
        source_key: sourceAudio.id,
        createdById: actorId,
        updatedById: actorId,
      };
    });

    // Bulk create pages and audio tracks
    if (targetPagesPayload.length) {
      await db.tour_pages.bulkCreate(targetPagesPayload, {
        transaction,
        returning: false,
      });
    }

    if (targetAudioPayload.length) {
      await db.project_audio_tracks.bulkCreate(targetAudioPayload, {
        transaction,
        returning: false,
      });
    }

    return {
      pages_copied: sourcePages.length,
      audios_copied: sourceAudioTracks.length,
    };
  }
};
