# typed: true

require "open-uri"
require "tempfile"
require "base64"

class Domain::PostsController < DomainController
  extend T::Sig
  extend T::Helpers

  skip_before_action :authenticate_user!,
                     only: %i[
                       show
                       index
                       user_favorite_posts
                       user_created_posts
                       visual_search
                       visual_results
                     ]
  before_action :set_post!, only: %i[show]
  before_action :set_user!, only: %i[user_favorite_posts user_created_posts]
  before_action :set_post_group!, only: %i[posts_in_group]

  class PostsIndexViewConfig < T::ImmutableStruct
    include T::Struct::ActsAsComparable

    const :show_domain_filters, T::Boolean
    const :show_creator_links, T::Boolean
    const :index_type_header, String
  end

  sig { void }
  def initialize
    super
    @posts_index_view_config =
      PostsIndexViewConfig.new(
        show_domain_filters: false,
        show_creator_links: false,
        index_type_header: "all_posts",
      )
  end

  # GET /posts
  sig(:final) { void }
  def index
    @posts_index_view_config =
      PostsIndexViewConfig.new(
        show_domain_filters: true,
        show_creator_links: true,
        index_type_header: "all_posts",
      )
    authorize Domain::Post
    @posts = posts_relation(Domain::Post.all).without_count
    active_sources = (params[:sources] || DomainSourceHelper.all_source_names)
    unless DomainSourceHelper.has_all_sources?(active_sources)
      postable_types =
        DomainSourceHelper.source_names_to_class_names(active_sources)
      @posts = @posts.where(type: postable_types) if postable_types.any?
    end
  end

  # GET /posts/:id
  sig(:final) { void }
  def show
    authorize @post
  end

  sig(:final) { void }
  def user_favorite_posts
    @posts_index_view_config =
      PostsIndexViewConfig.new(
        show_domain_filters: false,
        show_creator_links: true,
        index_type_header: "user_favorites",
      )

    @user = T.must(@user)
    authorize @user

    @posts = @user.faved_posts.with_user_post_fav
    # raise @posts.to_sql

    # Apply pagination through posts_relation
    @posts = posts_relation(@posts, skip_ordering: true)

    authorize @posts
    render :index
  end

  sig(:final) { void }
  def user_created_posts
    @posts_index_view_config =
      PostsIndexViewConfig.new(
        show_domain_filters: false,
        show_creator_links: true,
        index_type_header: "user_created",
      )

    @user = T.must(@user)
    authorize @user
    @posts = posts_relation(@user.posts)
    authorize @posts
    render :index
  end

  sig(:final) { void }
  def posts_in_group
    @posts_index_view_config =
      PostsIndexViewConfig.new(
        show_domain_filters: false,
        show_creator_links: true,
        index_type_header: "posts_in_group",
      )

    authorize @post_group
    @posts = posts_relation(T.must(@post_group).posts)
    render :index
  end

  # GET /posts/visual_search
  sig(:final) { void }
  def visual_search
    authorize Domain::Post
  end

  sig { params(content_type: T.nilable(String)).returns(T::Boolean) }
  def check_content_type!(content_type)
    return false unless content_type

    ret =
      Domain::PostFile::Thumbnail::THUMBABLE_CONTENT_TYPES.any? do |type|
        content_type.match?(type)
      end

    unless ret
      flash.now[:error] = "The uploaded file is not a valid image format."
      render :visual_search
    end

    ret
  end

  # POST /posts/visual_search
  sig(:final) { void }
  def visual_results
    authorize Domain::Post

    # Process the uploaded image or URL
    image_result = process_image_input
    return unless image_result

    image_path, content_type = image_result

    # Create thumbnail for the view if possible
    @uploaded_image_data_uri = create_thumbnail(image_path, content_type)
    @uploaded_hash_value = generate_fingerprint(image_path)
    @uploaded_detail_hash_value = generate_detail_fingerprint(image_path)
    @post_file_fingerprints =
      helpers.find_similar_fingerprints(
        fingerprint_value: @uploaded_hash_value,
        fingerprint_detail_value: @uploaded_detail_hash_value,
      ).to_a
    @post_file_fingerprints = @post_file_fingerprints.take(10)
    @posts =
      @post_file_fingerprints
        .map(&:fingerprint)
        .map(&:post_file)
        .compact
        .map(&:post)
  ensure
    # Clean up any temporary files
    if @temp_file
      @temp_file.unlink
      @temp_file = nil
    end
  end

  private

  # Process the uploaded file or URL and return [image_path, content_type] or nil on failure
  sig { returns(T.nilable([String, String])) }
  def process_image_input
    if params[:image_file].present?
      process_uploaded_file
    elsif params[:image_url].present?
      process_image_url
    else
      flash.now[:error] = "Please upload an image or provide an image URL."
      render :visual_search
      nil
    end
  end

  # Process an uploaded file and return [image_path, content_type] or nil on failure
  sig { returns(T.nilable([String, String])) }
  def process_uploaded_file
    image_file = params[:image_file]
    content_type = T.must(image_file.content_type)

    return nil unless check_content_type!(content_type)

    image_path = T.must(image_file.tempfile.path)
    [image_path, content_type]
  end

  # Process an image URL and return [image_path, content_type] or nil on failure
  sig { returns(T.nilable([String, String])) }
  def process_image_url
    # Download the image to a temporary file
    image_url = params[:image_url]
    image_io = URI.open(image_url)

    if image_io.nil?
      flash.now[:error] = "The URL does not point to a valid image format."
      render :visual_search
      return nil
    end

    content_type = T.must(T.unsafe(image_io).content_type)
    return nil unless check_content_type!(content_type)

    # Save to temp file
    extension = helpers.extension_for_content_type(content_type) || "jpg"
    @temp_file = Tempfile.new(["image", ".#{extension}"])
    @temp_file.binmode
    image_data = image_io.read
    @temp_file.write(image_data)
    @temp_file.close

    image_path = T.must(@temp_file.path)
    [image_path, content_type]
  rescue StandardError => e
    Rails.logger.error("Error processing image URL: #{e.message}")
    flash.now[:error] = "Error downloading search image"
    render :visual_search
    nil
  end

  # Create a thumbnail from the image and return the data URI
  sig do
    params(image_path: String, content_type: String).returns(T.nilable(String))
  end
  def create_thumbnail(image_path, content_type)
    helpers.create_image_thumbnail_data_uri(image_path, content_type)
  end

  # Generate a fingerprint from the image path
  sig { params(image_path: String).returns(String) }
  def generate_fingerprint(image_path)
    # Use the new from_file_path method to create a fingerprint
    Domain::PostFile::BitFingerprint.from_file_path(image_path)
  end

  # Generate a detail fingerprint from the image path
  sig { params(image_path: String).returns(String) }
  def generate_detail_fingerprint(image_path)
    Domain::PostFile::BitFingerprint.detail_from_file_path(image_path)
  end

  sig { override.returns(DomainController::DomainParamConfig) }
  def self.param_config
    DomainController::DomainParamConfig.new(
      post_id_param: :id,
      user_id_param: :domain_user_id,
      post_group_id_param: :domain_post_group_id,
    )
  end

  sig(:final) do
    params(
      starting_relation: ActiveRecord::Relation,
      skip_ordering: T::Boolean,
    ).returns(
      T.all(ActiveRecord::Relation, Kaminari::ActiveRecordRelationMethods),
    )
  end
  def posts_relation(starting_relation, skip_ordering: false)
    relation = starting_relation
    relation = T.unsafe(policy_scope(relation)).page(params[:page]).per(50)
    relation =
      relation.order(
        relation.klass.post_order_attribute => :desc,
      ) unless skip_ordering
    relation
  end
end
