# frozen_string_literal: true

# Respondent
class Respondent < ApplicationRecord
  has_neighbors :embedding
  has_many :respondent_tags, dependent: :destroy
  has_many :tags, through: :respondent_tags
  has_many :transcript_sections, dependent: :destroy
  has_many :transcript_highlights, -> { order(:id) }, inverse_of: :respondent, dependent: :destroy
  SCRIPT_PATH = "/home/fareesh/scripts/sprint"

  LANGUAGES = {
    "en": "english",
    "hi": "hindi",
    "ta": "tamil",
    "ml": "malayalam",
    "te": "telugu",
    "bn": "bengali",
    "kn": "kannada",
    "mr": "marathi",
    "pa": "punjabi",
    "gu": "gujarati",
    "as": "assamese",
    "od": "odia",
    "de": "german",
    "fr": "french",
    "es": "spanish",
    "it": "italian",
    "nl": "dutch",
    "ja": "japan",
    "ko": "korean",
    "zh": "chinese",
    "ar": "arabic",
    "ru": "russian",
    "pt": "portuguese",
    "multi": "hinglish"
  }.freeze

  MANUAL_LANGUAGES = {
    "en": "english",
    "hi": "hindi",
    "ta": "tamil",
    "ml": "malayalam",
    "te": "telugu",
    "bn": "bengali",
    "kn": "kannada",
    "mr": "marathi",
    "pa": "punjabi",
    "gu": "gujarati",
    "as": "assamese",
    "id": "bahasa",
    "od": "odia",
    "de": "german",
    "fr": "french",
    "es": "spanish",
    "it": "italian",
    "ko": "korean",
    "multi": "hinglish"
  }.freeze

  validates :first_name, presence: true

  # Respondent Category -> Questions
  # Respondent -> (Optional) Audio -> Transcript
  has_one :whisper_transcript, class_name: "WhisperTranscript", dependent: :destroy

  has_one :docx_transcript, dependent: :destroy

  has_many :transcript_sections, dependent: :destroy
  # Interview Insights
  has_many :interview_insights, dependent: :destroy
  belongs_to :respondent_category
  has_one :project, through: :respondent_category

  has_attached_file :transcript, validate_media_type: false
  has_attached_file :audio_file, validate_media_type: false
  has_attached_file :video_file, validate_media_type: false

  # Validate that the uploaded file is either a DOC or DOCX
  validates_attachment :transcript, content_type: { content_type: [
    "application/msword",
    "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
  ] }

  # Validate the file extension
  validates_attachment_file_name :transcript, matches: [/\.docx?\z/]

  do_not_validate_attachment_file_type :audio_file
  do_not_validate_attachment_file_type :video_file

  before_audio_file_post_process :rename_audio_file
  before_video_file_post_process :rename_video_file

  after_destroy :remove_system_tasks

  def transcript_changed?
    saved_change_to_transcript_file_name? || (previous_changes.key?("id") && transcript.present?)
  end

  def transcript_language
    return "en" if translate

    respondent_type == "transcript" ? manual_language : language
  end

  def generate_bookmarks!
    transcript_sections.destroy_all
    transcript_highlights.destroy_all

    model_map = FeatureModelMap.find_by(feature_name: "BOOKMARK_GENERATION")
    chat = RubyLLM.chat(provider: model_map.provider, model: model_map.llm_model_name)
    system_prompt = <<~SYSTEM_PROMPT
      You are a market research analyst. You will be given a transcript with individual sentences and line number for each sentence.
      You will also be given a dictionary of section headers with their ids.
      Your job is to analyze the transcript and divide it into relevant sections.

      The format of the transcript is JSON:
      {
        "0": "speaker_1: sentence 1"
        "1": "speaker_2: sentence 2"
        ... and so on
      }
      where the key is the line number and value is the sentence.

      The format of the sections map is JSON:
      {
        "1": "Introduction",
        "2": "Product sentiments",
        ... and so on
      }
      where key is the id and value is the section heading.

      To divide the transcript based on relevant sections, assign the id of the section to the relevant line number.
      For example - if line numbers 1 to 50 belong to section ID 2, then assign section ID 2 to line number 1.
      The section assigned to a line number determines the start of a section, and the end of the section is till the start of another section.
      You can assign the same section multiple times if the context of the transcript is relevant.

      Do not assign the same section consecutively, i.e. if lines 1-100 have Section 1 assigned, and lines 101-200 also have Section 1 assigned, then just assign
      Section 1 from line 1-200.

      It is not required that the section must start from the beginning of the transcript i.e. line number 1, if the context does not make sense, then no need to add a section.
      Only assign the sections if the section headers make sense with what the transcript is about.

      Try to divide the transcript using minimum number of sections if possible, while keeping the context the in mind.

      Return ONLY valid JSON where keys are line numbers (as strings) and values are section IDs (as strings).
      Example response: {"0": "1", "50": "2", "100": "1"}
    SYSTEM_PROMPT

    transcript = if respondent_type == "audio"
                   whisper_transcript.verbose_json.map.with_index { |line, i| [i.to_s, "#{line['speaker']}: #{line['text']}"] }.to_h
                 else
                   docx_transcript.verbose_json.map.with_index { |line, i| [i.to_s, line["text"]] }.to_h
                 end

    sections_map = respondent_category.master_sections.pluck(:id, :name).to_h.transform_keys(&:to_s)

    prompt = <<~PROMPT
      These are the sections:
      ```
      #{sections_map.to_json}
      ```

      The transcript:
      ```
      #{transcript.to_json}
      ```
    PROMPT

    chat.with_instructions(system_prompt)
    response = chat.ask(prompt)

    TranscriptSection.create_from_llm_bookmarks(self, response.content)

    task = SystemTask.where(task_type: "GenerateBookmarks").where("metadata @> ?",
                                                                  { respondent_id: id }.to_json).last
    task&.status = "complete"
    task&.running = false
    task&.save
    SystemTask.execute_next_task!(task_class: task&.task_class)
  end

  def store_embedding!
    model_map = FeatureModelMap.for_feature!("TRANSCRIPT_EMBEDDINGS")
    response = RubyLLM.embed(full_text.to_s, model: model_map.llm_model_name, provider: model_map.provider)
    self.embedding = response.vectors
    save
  end

  def summarize!
    model_map = FeatureModelMap.find_by(feature_name: "TRANSCRIPT_SUMMARIZER")
    chat = RubyLLM.chat(provider: model_map.provider, model: model_map.llm_model_name)
    instruction = Instruction.find_by(key: "TRANSCRIPT_SUMMARIZER")
    return if instruction.nil?

    system_prompt = instruction.system_prompt
    language_prompt = " Generate the summary in #{Respondent::LANGUAGES.transform_values(&:titleize)[transcript_language.to_sym]}."
    prompt = respondent_category.transcript_summary_prompt.concat(language_prompt)
    prompt = <<~PROMPT
      #{prompt}
      <transcript>
        #{full_text}
      </transcript>
    PROMPT
    chat.with_instructions(system_prompt)
    response = chat.ask(prompt)
    self.summary = response.content
    save

    task = SystemTask.where(task_type: "TranscriptSummary").where("metadata @> ?",
                                                                  { respondent_id: id }.to_json).last
    task&.status = "complete"
    task&.running = false
    task&.save
    SystemTask.execute_next_task!(task_class: task&.task_class)

    # Update summary button
    broadcast_replace_to(
      [self, "tasks"],
      target: "summary_button_#{id}",
      partial: "respondents/summary_button",
      locals: {
        respondent: self,
        project: project,
        respondent_category: respondent_category
      }
    )

    # update actual summary
    broadcast_replace_to(
      [self, "tasks"],
      target: "summary_content_#{id}",
      partial: "respondents/summary_content",
      locals: {
        respondent: self
      }
    )
  end

  def uploaded_transcript_path
    return "gs://sprint-qualplatform/#{transcript.path}" if !docx_transcript.nil? && !docx_transcript.translated

    "gs://sprint-qualplatform/uploaded_transcripts/transcript_original_#{id}.docx"
  end

  def upload_transcript_to_gcs
    require "caracal"
    require "tempfile"

    if !docx_transcript.nil?
      if docx_transcript.translated
        begin
          Tempfile.create(["temp_transcript", ".docx"]) do |file|
            Caracal::Document.save(file.path) do |doc|
              doc.h1 name, bold: true, size: 32
              doc.p
              docx_transcript.verbose_json.each do |sentence|
                # Replace any remaining single \n with spaces if needed
                cleaned_text = sentence["text"].gsub("\n", " ").strip
                doc.p cleaned_text
                doc.p
              end
            end
            remote_path = "uploaded_transcripts/transcript_original_#{id}.docx"
            # Upload to GCS
            GoogleCloudStorageService.upload_file(
              file.path,
              bucket_name: "sprint-qualplatform",
              file_path: remote_path
            )
          end
          remote_path
        rescue StandardError => e
          Rails.logger.fatal("Translated Docx Transcript download error: #{e.inspect}")
        end
      else
        # Return transcript URL
        "gs://sprint-qualplatform/#{transcript.path}"
      end
    elsif !whisper_transcript.nil?
      begin
        Tempfile.create(["temp_transcript", ".docx"]) do |file|
          Caracal::Document.save(file.path) do |doc|
            doc.h1 name, bold: true, size: 32, bottom: 240 # Add some space after the header too

            whisper_transcript.verbose_to_text.split("\n\n").each do |paragraph|
              cleaned_text = sanitize_for_docx(paragraph)

              doc.p cleaned_text, bottom: 200 if cleaned_text.present?
            end
          end
          # Upload to GCS
          remote_path = "uploaded_transcripts/transcript_original_#{id}.docx"
          SprintGCS.upload(
            file: file.path,
            bucket_name: "sprint-qualplatform",
            path: remote_path
          )
          remote_path
        end
      rescue StandardError => e
        Rails.logger.fatal("Whisper Transcript download error: #{e.inspect}")
      end
    end
  end

  def remove_system_tasks
    tasks = SystemTask.where("metadata @> ?", { respondent_id: id }.to_json)
    Rails.logger.fatal "Destroying #{tasks.count} tasks"
    # SystemTask.where("metadata @> ?", { respondent_id: id }).destroy_all
  end

  def type_manual_transcript?
    !docx_transcript.nil? && whisper_transcript.nil?
  end

  def type_ai_transcript?
    !whisper_transcript.nil? && docx_transcript.nil?
  end

  def visible_to?(user)
    return false if user.nil?

    return true if user.admin?

    user.projects.exists?(id: project.id)
  end

  def mapped_name(label)
    speaker_map.with_indifferent_access[label] || label
  end

  def full_text
    text = ""
    if docx_transcript
      text = docx_transcript.verbose_to_text
    elsif whisper_transcript
      text = whisper_transcript.verbose_to_text
    end
    text
  end

  def signed_audio_url
    SprintGCS.signed_url(
      bucket_name: "sprint-qualplatform",
      file_path: audio_file.path
    )
  end

  def signed_video_url
    SprintGCS.signed_url(
      bucket_name: "sprint-qualplatform",
      file_path: video_file.path
    )
  end

  def signed_transcript_url
    SprintGCS.signed_url(
      bucket_name: "sprint-qualplatform",
      file_path: transcript.path
    )
  end

  def displayed_speaker_labels
    original_speaker_map.keys.index_with do |original_speaker|
      speaker_map[original_speaker] || original_speaker
    end
  end

  def mapped_labels(transcript = whisper_transcript)
    speakers = transcript.verbose_to_mapped.collect { |x| x[:speaker] }.uniq
    result = {}
    speakers.each do |speaker|
      result[speaker] = speaker_map[speaker] || speaker
    end
    result
  end

  def initial_prompt
    if proper_nouns.blank?
      "The following is an interview conversation"
    else
      "The following is an interview conversation which includes the following words and proper nouns: #{proper_nouns.join(', ')}"
    end
  end

  def transcribe!(task_id)
    require "net/http"
    require "uri"
    require "json"
    require "ipaddr"

    if CloudServices.instance_state != "running"
      CloudServices.start_instance
      sleep(60)
    end

    instance_ip = CloudServices.instance_ip

    # Validate that instance_ip is a proper IP address (prevents SSRF)
    begin
      ip = IPAddr.new(instance_ip)
      unless ip.ipv4? || ip.ipv6?
        Rails.logger.fatal("[TRANSCRIBE] Invalid IP address: #{instance_ip}")
        return
      end
    rescue IPAddr::InvalidAddressError
      Rails.logger.fatal("[TRANSCRIBE] Invalid IP address format: #{instance_ip}")
      return
    end

    # Stateless Link
    # Always returns 201 (Done)
    transcribe_uri = URI.parse("http://#{instance_ip}:5000/transcribe")
    Rails.logger.fatal("Transcribing at #{transcribe_uri}")
    conversation_id = id

    # We pass the URL of the audio, ID of the respondent, and the update URL
    audio_url = audio_file.url
    update_url = "https://analysis.sprintstudio.ai/api/update_task_status/#{task_id}"

    task = translate ? "translate" : "transcribe"

    header = { 'Content-Type': "application/json" }
    data = {
      "conversation_id" => conversation_id,
      "initial_prompt" => initial_prompt,
      "audio_url" => audio_url,
      "language" => language,
      "model" => internal_model,
      "update_url" => update_url,
      "task" => task
    }

    Rails.logger.fatal("[TRANSCRIBE] Sending #{data}")

    http = Net::HTTP.new(transcribe_uri.host, transcribe_uri.port)
    request = Net::HTTP::Post.new(transcribe_uri.request_uri, header)
    request.body = data.to_json

    response = http.request(request)
    Rails.logger.fatal JSON.parse(response.body)
  end

  def internal_model
    if asr_model == "whisper-large-v3"
      "large-v3"
    else
      "large-v2"
    end
  end

  # gemini-flash-002
  def transcribe_v2!(task_id)
    if CloudServices.instance_state != "running"
      CloudServices.start_instance
      sleep(60)
    end

    instance_ip = "13.203.78.163"

    require "net/http"
    require "uri"
    require "json"

    # Stateless Link
    # Always returns 201 (Done)
    transcribe_url = "http://#{instance_ip}:5000/process_audio"
    conversation_id = id

    # We pass the URL of the audio, ID of the respondent, and the update URL
    audio_url = audio_file.url
    update_url = "https://analysis.sprintstudio.ai/api/update_task_status/#{task_id}" # change
    uri = URI.parse(transcribe_url)

    task = translate ? "translate" : "transcribe"

    header = { 'Content-Type': "application/json" }
    data = {
      "conversation_id" => conversation_id,
      "initial_prompt" => initial_prompt,
      "audio_url" => audio_url,
      "language" => language,
      "update_url" => update_url,
      "task" => task
    }

    Rails.logger.fatal("[TRANSCRIBE] Sending #{data}")
    http = Net::HTTP.new(uri.host, uri.port)
    request = Net::HTTP::Post.new(uri.request_uri, header)
    request.body = data.to_json

    response = http.request(request)
    Rails.logger.fatal JSON.parse(response.body)
  end

  def sarvam_transcribe!(task_id)
    require "net/http"
    require "uri"
    require "json"

    instance_ip = "10.160.0.8"

    # Stateless Link
    # Always returns 201 (Done)
    transcribe_uri = URI.parse("http://#{instance_ip}:5000/transcribe")
    Rails.logger.fatal("Transcribing at #{transcribe_uri}")
    conversation_id = id

    # We pass the URL of the audio, ID of the respondent, and the update URL
    audio_url = audio_file.url
    update_url = "https://analysis.sprintstudio.ai/api/update_task_status/#{task_id}"

    task = translate ? "translate" : "transcribe"

    header = { 'Content-Type': "application/json" }
    data = {
      "conversation_id" => conversation_id,
      "initial_prompt" => initial_prompt,
      "audio_url" => audio_url,
      "speaker_count" => speaker_count,
      "update_url" => update_url,
      "task" => task
    }

    Rails.logger.fatal("[TRANSCRIBE] Sending #{data}")

    http = Net::HTTP.new(transcribe_uri.host, transcribe_uri.port)
    request = Net::HTTP::Post.new(transcribe_uri.request_uri, header)
    request.body = data.to_json

    response = http.request(request)
    Rails.logger.fatal JSON.parse(response.body)
  end

  def self.format_deepgram_result(result)
    words = result["channels"].first["alternatives"].first["words"]
    result_chunks = []

    current_speaker = nil
    current_chunk = nil
    current_words = []
    current_text = ""

    words.each_with_index do |word, _i|
      word_speaker = word["speaker"]
      punct_word = word["punctuated_word"] || word["word"]

      # Speaker switch or start of a new chunk
      if current_speaker != word_speaker
        if current_chunk
          current_chunk[:end] = current_words.last["end"]
          current_chunk[:text] = current_text.strip
          current_chunk[:words] = current_words.map { |w| w.merge("speaker" => "Speaker #{w['speaker']}") }
          result_chunks << current_chunk
        end

        current_speaker = word_speaker
        current_chunk = {
          speaker: "Speaker #{current_speaker}",
          start: word["start"],
          end: nil,
          text: "",
          words: []
        }
        current_words = []
        current_text = ""
      end

      current_words << word
      current_text += "#{punct_word} "
    end

    # Add final chunk
    if current_chunk
      current_chunk[:end] = current_words.last["end"]
      current_chunk[:text] = current_text.strip
      current_chunk[:words] = current_words.map { |w| w.merge("speaker" => "Speaker #{w['speaker']}") }
      result_chunks << current_chunk
    end

    result_chunks
  end

  def self.format_transcript(transcript_json)
    transcript_json["segments"].map do |segment|
      {
        "start" => segment["start"],
        "end" => segment["end"],
        "text" => segment["text"],
        "words" => segment["words"].map do |word|
          {
            "start" => word["start"],
            "end" => word["end"],
            "word" => word["word"],
            "score" => word["probability"],
            "speaker" => segment["speaker"]
          }
        end,
        "speaker" => segment["speaker"]
      }
    end
  end

  def name
    "#{first_name} #{last_name}"
  end

  def generate_docx_transcript!
    return if transcript_file_name.nil?

    # return unless File.exist?(transcript.path)

    Rails.logger.debug("[GENERATE DOCX] .docx url for download - #{transcript.url(:original, timestamp: false)}")
    docx_transcript.destroy if docx_transcript&.verbose_json == []

    temp_transcript = download_docx_file(url: transcript.url(:original, timestamp: false))

    return if temp_transcript.nil?

    require "shellwords"
    # txt = `#{SCRIPT_PATH}/docx_converter.sh #{Shellwords.escape temp_transcript.path}`.encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
    txt = `/home/fareesh/.local/bin/docx2txt #{Shellwords.escape temp_transcript.path}`.encode("UTF-8",
                                                                                               invalid: :replace, undef: :replace, replace: "")

    txt = txt.chars.select(&:valid_encoding?).join

    docx_transcript&.destroy
    json = txt.split("\n").collect { |x| { text: x } }.reject { |x| x[:text].blank? }
    transcript_sections.destroy_all

    DocxTranscript.create!(
      respondent_id: id,
      verbose_json: json
    )

    temp_transcript.close
    temp_transcript.unlink
  end

  def css_tags
    tags.map { |x| "tag_#{x.id}" }.join(" ")
  end

  def questions_json(respondent_category_id:, question_ids: [])
    respondent_category = RespondentCategory.find(respondent_category_id)
    question_ids = respondent_category.interview_question_ids if question_ids.empty?
    result = []
    InterviewQuestion.where(id: question_ids)&.each do |question|
      if question.master_section
        section_data = transcript_sections.where(master_section_id: question.master_section.id).first
        result << if section_data
                    { question: question.question, id: question.id, section: section_data }
                  else
                    { question: question.question, id: question.id }
                  end
      else
        result << { question: question.question, id: question.id }
      end
    end
    result
  end

  def generate_transcript(current_user)
    require "shellwords"

    duration = 0

    begin
      duration = `ffprobe -v error -show_entries format=duration -of default=noprint_wrappers=1:nokey=1 -i #{Shellwords.escape(audio_file.url)}`.strip.to_f / 60
      Rails.logger.debug("Audio Duration - #{duration} minutes")
    rescue StandardError => e
      Rails.logger.fatal("Error with Duration #{e.inspect}")

      return {
        success: false,
        reason: :duration_check_failed
      }
    end

    case asr_model
    when "whisper-large-v3", "whisper-large-v2", "gemini-flash-002"
      return {
        success: false,
        reason: :model_unsupported
      }

    when "deepgram-nova"
      InternalTranscriptionV3Job.perform_later(id, current_user.id)

    when "gladia"
      if duration > 135
        return {
          success: false,
          reason: :duration_limit_exceeded,
          duration: duration
        }
      end

      InternalTranscriptionV4Job.perform_later(id, current_user.id)

    when "sarvam"
      InternalTranscriptionV5Job.perform_later(id, current_user.id)

    when "voxtral"
      InternalTranscriptionV6Job.perform_later(id, current_user.id)
    end

    { success: true }
  end

  def any_archived_transcripts?
    WhisperTranscript.only_deleted.where(respondent_id: id).count.positive?
  end

  def transcript_available?
    !whisper_transcript.nil? or !docx_transcript.nil?
  end

  def respondent_type
    if !docx_transcript.nil? && whisper_transcript.nil?
      "transcript"
    elsif !whisper_transcript.nil? && docx_transcript.nil?
      "audio"
    else
      "audio"
    end
  end

  def media_type
    if video_file_file_name.present?
      "video"
    elsif audio_file_file_name.present?
      "audio"
    end
  end

  def initial_transcribing_progress
    if asr_model == "sarvam"
      sarvam_task = SystemTask.where(task_type: %w[TranscribeV5])
                              .where("metadata @> ?",
                                     { respondent_id: id }.to_json).last
      if sarvam_task.nil?
        0
      else
        case sarvam_task.status.downcase
        when "running", "queued", "downloading"
          0
        when "converting"
          25
        when "splitting"
          50
        when "transcribing"
          75
        when "complete"
          100
        else
          0
        end
      end
    else
      task = SystemTask.where(task_type: %w[WxTranscribe TranscribeV2 TranscribeV3 TranscribeV4 TranscribeV6])
                       .where("metadata @> ?",
                              { respondent_id: id }.to_json).last
      if task.nil?
        0
      else
        case task.status.downcase
        when "running", "queued", "downloading", "converting"
          0
        when "transcribing"
          25
        when "cleaning"
          35
        when "aligning"
          50
        when "diarizing"
          75
        when "complete"
          100
        else
          0
        end
      end
    end
  end

  def transcribing?
    task = SystemTask.where(task_type: %w[WxTranscribe TranscribeV2 TranscribeV3 TranscribeV4 TranscribeV5 TranscribeV6]).where("metadata @> ?",
                                                                                                                                { respondent_id: id }.to_json).last
    return false if task.nil?

    %w[queued running downloading converting transcribing cleaning aligning diarizing splitting].include?(task.status.downcase)
  end

  def transcribing_complete?
    task = SystemTask.where(task_type: %w[WxTranscribe TranscribeV2 TranscribeV3 TranscribeV4 TranscribeV5 TranscribeV6]).where("metadata @> ?",
                                                                                                                                { respondent_id: id }.to_json).last
    return false if task.nil?

    task.status.downcase == "complete"
  end

  def generating_bookmarks?
    task = SystemTask.where(task_type: "GenerateBookmarks").where("metadata @> ?",
                                                                  { respondent_id: id }.to_json).last
    return false if task.nil?

    %w[queued running].include?(task.status.downcase)
  end

  def summarizing?
    task = SystemTask.where(task_type: "TranscriptSummary").where("metadata @> ?",
                                                                  { respondent_id: id }.to_json).last
    return false if task.nil?

    %w[queued running].include?(task.status.downcase)
  end

  def summary_markdown
    ApplicationController.helpers.markdown_render(summary)
  end

  def model_with_no_progression?
    %w[deepgram-nova gladia voxtral].include?(asr_model)
  end

  def queue_video_conversion(user_id:)
    VideoToAudioJob.perform_later(
      metadata: { respondent_id: id }, user_id:
    )
  end

  def convert_video_to_audio(system_task_id:)
    require "google/cloud/run/v2"
    video_gcs_path = video_file.path
    video_gcs_uri = "gs://sprint-qualplatform/#{video_gcs_path}"
    output_gcs_uri = "#{File.dirname(video_gcs_uri)}/#{File.basename(video_gcs_uri, '.*')}.mp3"
    output_gcs_uri = output_gcs_uri.gsub("video_files", "audio_files")

    job_type = "VIDEO_TO_AUDIO"
    notification_url = "https://analysis.sprintstudio.ai/api/cloud_job_finish"
    error_notification_url = "https://analysis.sprintstudio.ai/api/cloud_job_error"

    client = Google::Cloud::Run::V2::Jobs::Client.new
    project_id = "norse-baton-398712"
    region = "asia-south1"
    parent = "projects/#{project_id}/locations/#{region}"
    job_name = "video-processor-job"

    json_payload = {
      system_task_id:,
      respondent_id: id,
      video_gcs_uri:,
      output_gcs_uri:,
      job_type:,
      notification_url:,
      error_notification_url:
    }.to_json
    Rails.logger.debug(json_payload)

    request = Google::Cloud::Run::V2::RunJobRequest.new(
      name: "#{parent}/jobs/#{job_name}",
      overrides: {
        container_overrides: [
          {
            args: [json_payload]
          }
        ]
      }
    )

    operation = client.run_job request
    Rails.logger.info "Started Cloud Run Job. Operation name: #{operation.name}"
  rescue Google::Cloud::Error => e
    Rails.logger.error "Failed to run Cloud Run Job: #{e.message}"
  end

  def model_display_name
    {
      "whisper-large-v3" => "M1",
      "whisper-large-v2" => "M1B",
      "gemini-flash-002" => "M2",
      "deepgram-nova" => "M3",
      "gladia" => "M4",
      "sarvam" => "M5",
      "voxtral" => "M6"
    }.fetch(asr_model, "Unknown")
  end

  def sanitize_for_docx(text)
    return "" if text.blank?

    # 1. Force to valid UTF-8, replacing invalid byte sequences.
    sanitized_text = text.encode("UTF-8", "UTF-8",
                                 invalid: :replace,
                                 undef: :replace,
                                 replace: "")

    # 2. Replace the common non-breaking space with a regular space.
    sanitized_text.gsub!("\u00A0", " ")

    # 3. Remove common zero-width and other problematic characters.
    #    U+200B -> Zero-width space
    #    U+200C -> Zero-width non-joiner
    #    U+200D -> Zero-width joiner
    #    U+FEFF -> Byte order mark
    sanitized_text.gsub!(/[\u200B-\u200D\uFEFF]/, "")

    # 4. Remove whitespace
    sanitized_text.strip
  end

  def test_gcs_path(media_type:, extension:)
    require "securerandom"
    id_partition = format("%09d", id).scan(/\d{3}/).join("/")

    uuid = SecureRandom.uuid

    if media_type == "audio"
      ext = extension || "mp3"
      "temp_uploads/audio_files/#{id_partition}/#{id}_#{uuid}.#{ext}"
    elsif media_type == "video"
      ext = extension || "mp4"
      "temp_uploads/video_files/#{id_partition}/#{id}_#{uuid}.#{ext}"
    else
      ext = extension || "docx"
      "temp_uploads/transcripts/#{id_partition}/#{id}_#{uuid}.#{ext}"
    end
  end

  def attachment_gcs_path(media_type:, extension:)
    require "securerandom"
    id_partition = format("%09d", id).scan(/\d{3}/).join("/")

    uuid = SecureRandom.uuid

    if media_type == "audio"
      ext = extension || "mp3"
      "respondents/audio_files/#{id_partition}/original/#{id}_#{uuid}.#{ext}"
    elsif media_type == "video"
      ext = extension || "mp4"
      "respondents/video_files/#{id_partition}/original/#{id}_#{uuid}.#{ext}"
    else
      ext = extension || "docx"
      "respondents/transcripts/#{id_partition}/original/#{id}.#{ext}"
    end
  end

  private

  def rename_audio_file
    return if audio_file_file_name.blank?

    timestamp = Time.now.to_i
    new_file_name = "#{id}_#{timestamp}#{File.extname(audio_file_file_name)}"
    audio_file.instance_write(:file_name, new_file_name)
  end

  def rename_video_file
    return if video_file_file_name.blank?

    timestamp = Time.now.to_i
    new_file_name = "#{id}_#{timestamp}#{File.extname(video_file_file_name)}"
    video_file.instance_write(:file_name, new_file_name)
  end

  def download_docx_file(url:)
    require "open-uri"
    require "tempfile"
    require "open3"

    return nil unless URI.parse(url).host == "cscdn.sprintstudio.ai"

    temp_file = Tempfile.new(["downloaded_file", File.extname(url)])
    begin
      command = ["wget", "-O", temp_file.path, url]
      stdout, stderr, status = Open3.capture3(*command)
      Rails.logger.debug("[GENERATE DOCX] Generated Temp Path - #{temp_file.path}")
      temp_file
    rescue StandardError => e
      Rails.logger.fatal("Error during .docx file download -> #{e}")
      temp_file.close
      temp_file.unlink
      nil
    end
  end
end
