# frozen_string_literal: true

# System Tasks
class SystemTask < ApplicationRecord
  include ActionView::RecordIdentifier
  belongs_to :user

  before_create :assign_task_class!

  PRETTY_NAMES = {
    "TranscribeV3" => "Transcription",
    "TranscribeV4" => "Transcription",
    "TranscribeV5" => "Transcription",
    "VectorAnalysis" => "Analysis"
  }.freeze

  def self.execute_stuck_tasks!
    # Use a database transaction with row-level locking
    SystemTask.transaction do
      # Check if there are any running tasks, with a lock to prevent changes during this check
      no_running_tasks = !SystemTask.lock.exists?(running: true)

      # Only proceed if no tasks are running
      if no_running_tasks
        # Find tasks that have been queued, with a lock
        stuck_task = SystemTask.where(status: "queued")
                               .lock
                               .order(created_at: :asc)
                               .first

        SystemTask.execute_next_task!(task_class: stuck_task.task_class) if stuck_task
      end
    end
  end

  def cancel_task
    self.status = "cancelled"
    self.running = false
    save
    Rails.logger.info "[TASKS] Cancelled task - #{id}"
    SystemTask.execute_next_task!(task_class:)
  end

  def pretty_name
    PRETTY_NAMES[task_type] || task_type
  end

  def self.vector_analysis_running_for_respondent(respondent)
    SystemTask.where(running: true).where(task_type: "VectorAnalysis").where("metadata->>'respondent_id' = ?",
                                                                             respondent.id.to_s).count.positive?
  end

  def enqueue!
    if SystemTask.where.not(id:).where(running: true, task_class:).count.positive?
      self.status = "queued"
      self.running = false
      save
    else
      self.status = "running"
      self.running = true
      save
      execute!
    end
  end

  def assign_task_class!
    class_map = {
      "SectionReportSummary" => "SectionReportLLM",
      "GenerateAssistantV2" => "LLM",
      "InsightGeneration" => "InsightReportLLM",
      "InsightsReportSummary" => "InsightReportLLM",
      "TranscriptSummary" => "LLM",
      "GenerateBookmarks" => "LLM",
      "GenerateSectionsDG" => "LLM",
      "GenerateQueriesDG" => "LLM",
      "ReelCreation" => "GCP",
      "StitchedReelCreation" => "GCP",
      "VideoConversion" => "GCP",
      "TranscribeV3" => "Deepgram",
      "TranscribeV4" => "Gladia",
      "TranscribeV5" => "Sarvam",
      "TranscribeV6" => "Voxtral"
    }
    self.task_class = class_map[task_type]
  end

  def set_progress!(org_id:, progress:, metadata:, is_assistant: false)
    self.progress = progress
    save
    TasksChannel.send_data(org_id, { id:, progress:, metadata: })
    Rails.logger.info "[TASKS] Progress sent to: #{dom_id(self, 'progress')}"
    broadcast_replace_to(
      [self, "progress"],
      target: dom_id(self, "progress"),
      partial: "system_tasks/progress",
      locals: { system_task: self }.merge(metadata)
    )
    if self.progress == 100 && is_assistant
      # Broadcast the button to the new container
      broadcast_replace_to(
        "open-assistant-button",
        target: "open-assistant-button",
        partial: "#{metadata[:raggable_type] == 'RespondentCategory' ? 'respondent_categories' : 'projects'}/open_assistant_button",
        locals: {
          project_id: metadata[:project_id],
          respondent_category_id: metadata[:respondent_category_id]
        }
      )
    end
  rescue StandardError => e
    Rails.logger.fatal("Error setting task progress - #{e.inspect}")
  end

  def execute!(force: false)
    if SystemTask.where.not(id:).where(running: true, task_class:).count.positive? && !force
      self.status = "queued"
      self.running = false
      save
      Rails.logger.fatal("Not executing Task ##{id} because same class is already running")
      return
    end
    Rails.logger.fatal "Executing!"
    case task_type
    when "GenerateAssistantV2"
      begin
        md = metadata.with_indifferent_access
        @project = Project.find(md[:project_id])
        @respondent_category = RespondentCategory.find(md[:respondent_category_id]) if md[:respondent_category_id]
        raggable = if md[:raggable_type] == "RespondentCategory"
                     @respondent_category
                   else
                     @project
                   end
        conversation = AssistantConversation.create(
          user_id:,
          raggable:
        )
        RagAssistant.new(conversation, task_id: id, project_id: @project.id,
                                       respondent_category_id: (@respondent_category.id if md[:respondent_category_id]))
        self.running = false
        self.status = "complete"
        save
        SystemTask.execute_next_task!(task_class:)
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] GenerateAssistantV2 Task Failed -> #{e.inspect}")
        # TeamsAlertJob.perform_later(message: "[TASKS] GenerateAssistantV2 Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "QuantifiableQuestion"
      md = metadata.with_indifferent_access
      quantifiable_question_id = md["quantifiable_question_id"]
      qq = QuantifiableQuestion.find(quantifiable_question_id)
      qq.execute_script!
      self.running = false
      self.status = "complete"
      save
      SystemTask.execute_next_task!(task_class:)
      return
    when "SectionReportSummary"
      begin
        md = metadata.with_indifferent_access
        is_all_data = md[:is_all_data]
        if is_all_data
          SectionsReport.summarize_all_data_report(task_id: id,
                                                   respondent_category_id: md[:respondent_category_id])
        else
          SectionsReport.summarize_report(task_id: id, section_report_id: md[:sections_report_id])
        end
        self.running = false
        self.status = "complete"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] SectionReportSummary Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] SectionReportSummary Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "TranscriptSummary"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] TranscriptSummary Task Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          respondent.summarize!
        end
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] TranscriptSummary Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "TranscriptSummary Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "GenerateBookmarks"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] GenerateBookmarks Task Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          respondent.generate_bookmarks!
        end
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] GenerateBookmarks Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "GenerateBookmarks Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "GenerateSectionsDG"
      begin
        md = metadata.with_indifferent_access
        MasterSection.generate_sections_with_dg(md[:dg_text], md[:project_id], md[:respondent_category_id])
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] GenerateSectionsDG Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "GenerateSectionsDG Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "GenerateQueriesDG"
      begin
        md = metadata.with_indifferent_access
        InterviewQuestion.generate_questions_with_dg(md[:dg_text], md[:respondent_category_id])
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] GenerateQueriesDG Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "GenerateQueriesDG Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "TranscribeV3"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] TranscribeV3 Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          Deepgram.transcribe(respondent, language: respondent.language)
        end
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] TranscribeV3 Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "TranscribeV3 Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "TranscribeV4"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] TranscribeV4 Task Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          data = Gladia.transcribe(respondent, language: respondent.language, translation: respondent.translate)
          self.request_id = data.with_indifferent_access["id"]
          save
        end
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] TranscribeV4 Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] TranscribeV4 Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
      # Here we don't manage the task because it happens on callback from the other server
    when "TranscribeV5"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] TranscribeV5 Task Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          respondent.sarvam_transcribe!(id)
        end
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] TranscribeV5 Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] TranscribeV5 Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "TranscribeV6"
      begin
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] TranscribeV6 Task Failed -> Respondent: #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
          save
          SystemTask.execute_next_task!(task_class:)
          return
        else
          Mistral.transcribe(respondent, self)
        end
      rescue StandardError => e
        error_message = "[TASKS] TranscribeV6 Task Failed -> #{e.message} | #{e.backtrace.first(3).join(' | ')}"

        Rails.logger.fatal("[TASKS] TranscribeV6 Task Failed -> #{error_message}")
        TeamsAlertJob.perform_later(message: "[TASKS] TranscribeV6 Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "InsightGeneration"
      begin
        md = metadata.with_indifferent_access
        is_all_data = md[:is_all_data]
        if is_all_data
          InsightsReport.generate_insights(is_all_data:, respondent_category_id: md[:respondent_category_id], insights_report_id: nil, system_task_id: id)
        else
          InsightsReport.generate_insights(respondent_category_id: md[:respondent_category_id], insights_report_id: md[:insights_report_id],
                                           is_all_data:, system_task_id: id)
        end
        self.running = false
        self.status = "complete"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] VectorAnalysis Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] VectorAnalysis Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "InsightsReportSummary"
      begin
        md = metadata.with_indifferent_access
        insights_report_id = md[:insights_report_id]
        is_all_data = md[:is_all_data]
        insights_report = InsightsReport.find(insights_report_id) unless is_all_data
        if is_all_data
          InsightsReport.generate_summaries(is_all_data:, respondent_category_id: md[:respondent_category_id], insights_report_id: nil, system_task_id: id)
        elsif insights_report.nil?
          Rails.logger.fatal("[TASKS] InsightsReportSummary Task Failed -> Insights Report : #{md[:insights_report_id]} doesn't exist.")
          self.running = false
          self.status = "error"
        else
          InsightsReport.generate_summaries(is_all_data:, insights_report_id:, respondent_category_id: md[:respondent_category_id], system_task_id: id)
        end
        self.running = false
        self.status = "complete"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] InsightsReportSummary Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] InsightsReportSummary Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "ReelCreation"
      begin
        Rails.logger.fatal "Reel Creation!"
        md = metadata.with_indifferent_access
        transcript_highlight = TranscriptHighlight.find(md[:transcript_highlight_id])
        if transcript_highlight.nil?
          Rails.logger.fatal("[TASKS] ReelCreation Task Failed -> Highlight : #{md[:transcript_highlight_id]} doesn't exist.")
          self.running = false
          self.status = "error"
        else
          transcript_highlight.create_reel(system_task_id: id)
        end
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] ReelCreation Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] ReelCreation Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "StitchedReelCreation"
      begin
        Rails.logger.fatal "Stitched Reel Creation!"
        md = metadata.with_indifferent_access
        highlight_ids = md[:highlight_ids]
        reel = Reel.find_by(id: md[:reel_id])
        if reel.nil?
          Rails.logger.fatal("[TASKS] StitchedReelCreation Task Failed -> Reel : #{md[:reel_id]} doesn't exist.")
          self.running = false
          self.status = "error"
        else
          reel.stitch_reels(system_task_id: id, reel_id: md[:reel_id], highlight_ids:,
                            subtitles_enabled: md[:subtitles_enabled])
        end
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] StitchedReelCreation Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] StitchedReelCreation Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    when "VideoConversion"
      begin
        Rails.logger.fatal "Video to Audio Conversion!"
        md = metadata.with_indifferent_access
        respondent = Respondent.find(md[:respondent_id])
        if respondent.nil?
          Rails.logger.fatal("[TASKS] VideoConversion Task Failed -> Respondent : #{md[:respondent_id]} doesn't exist.")
          self.running = false
          self.status = "error"
        else
          respondent.convert_video_to_audio(system_task_id: id)
        end
        save
        SystemTask.execute_next_task!(task_class:)
        return
      rescue StandardError => e
        Rails.logger.fatal("[TASKS] VideoConversion Task Failed -> #{e.inspect}")
        TeamsAlertJob.perform_later(message: "[TASKS] VideoConversion Task Failed -> #{e.inspect}")
        self.running = false
        self.status = "error"
        save
        SystemTask.execute_next_task!(task_class:)
        return
      end
    end
    save
  end

  def self.execute_next_task!(task_class:)
    task = SystemTask.transaction do
      Rails.logger.info "[TASKS] Checking for running tasks of class: #{task_class}"

      # First check if anything is running
      if SystemTask.where(task_class:, running: true).exists?
        Rails.logger.info "[TASKS] Found running task for class: #{task_class}, skipping"
        return
      end

      Rails.logger.info "[TASKS] No running tasks found for class: #{task_class}, looking for pending tasks"

      # Find the first queued task with a lock to prevent race conditions
      if (next_pending_task = SystemTask.lock
                                       .where(task_class:, status: "queued")
                                       .first)

        Rails.logger.info "[TASKS] Found pending task ID: #{next_pending_task.id} for class: #{task_class}"

        # Update the task atomically
        next_pending_task.update!(
          running: true,
          status: "running"
        )
        Rails.logger.info "[TASKS] Updated task ID: #{next_pending_task.id} to running status"

        next_pending_task # Return the task from the transaction
      else
        Rails.logger.info "[TASKS] No pending tasks found for class: #{task_class}"
        nil
      end
    end

    # Execute outside the transaction
    if task
      Rails.logger.info "[TASKS] Executing task ID: #{task.id}"
      task.execute!
    end
  rescue StandardError => e
    Rails.logger.error "[TASKS] Error executing task for class #{task_class}: #{e.message}"
    Rails.logger.error "[TASKS] #{e.backtrace.join("\n")}"
    raise
  end
end
