# frozen_string_literal: true

#
# Copyright (C) 2025 - present Instructure, Inc.
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#

# @API AI Experiences
# API for creating, accessing and updating AI Experiences. AI Experiences are
# used to create interactive AI-powered learning scenarios within courses.
#
# @model AiExperience
#     {
#       "id": "AiExperience",
#       "description": "An AI Experience for interactive learning",
#       "properties": {
#         "id": {
#           "description": "The ID of the AI experience",
#           "example": 234,
#           "type": "integer"
#         },
#         "title": {
#           "description": "The title for the AI experience",
#           "example": "Customer Service Simulation",
#           "type": "string"
#         },
#         "description": {
#           "description": "The description of the AI experience",
#           "example": "Practice customer service skills in a simulated environment",
#           "type": "string"
#         },
#         "facts": {
#           "description": "The AI facts for the experience",
#           "example": "You are a customer service representative...",
#           "type": "string"
#         },
#         "learning_objective": {
#           "description": "The learning objectives for this experience",
#           "example": "Students will practice active listening and problem-solving",
#           "type": "string"
#         },
#         "scenario": {
#           "description": "The scenario description for the experience",
#           "example": "A customer is calling about a billing issue",
#           "type": "string"
#         },
#         "workflow_state": {
#           "description": "The current published state of the AI experience",
#           "example": "published",
#           "type": "string"
#         },
#         "course_id": {
#           "description": "The course this experience belongs to",
#           "example": 1578941,
#           "type": "integer"
#         }
#       }
#     }

class AiExperiencesController < ApplicationController
  include Api::V1::AiExperience

  protect_from_forgery except: %i[create update destroy continue_conversation], with: :exception

  before_action :require_context
  before_action :check_ai_experiences_feature_flag
  before_action :load_experience, only: %i[show edit update destroy continue_conversation]
  before_action :require_read_rights, only: %i[index show continue_conversation]
  before_action :require_manage_rights, only: %i[new create edit update destroy]

  # @API List AI experiences
  #
  # Retrieve the paginated list of AI experiences for a course
  #
  # @argument workflow_state [String]
  #   Only return experiences with the specified workflow state.
  #   Allowed values: published, unpublished, deleted
  #
  # @returns [AiExperience]
  def index
    @experiences = @context.ai_experiences.active
    @experiences = @experiences.where(workflow_state: params[:workflow_state]) if params[:workflow_state].present?
    respond_to do |format|
      format.html { render }
      format.json { render json: ai_experiences_json(@experiences, @current_user, session) }
    end
  end

  # @API Show an AI experience
  #
  # Retrieve an AI experience by ID
  #
  # @returns AiExperience
  def show
    @ai_experience = @experience
    set_active_tab "ai_experiences"
    add_crumb t("#crumbs.ai_experiences", "AI Experiences"), course_ai_experiences_path(@context)
    add_crumb @ai_experience.title
    respond_to do |format|
      format.html do
        @page_title = @ai_experience.title
        js_bundle :ai_experiences_show
        js_env(AI_EXPERIENCE: ai_experience_json(@ai_experience, @current_user, session))
        render html: view_context.content_tag(:div, nil, id: "ai_experiences_show"),
               layout: true
      end
      format.json { render json: ai_experience_json(@experience, @current_user, session) }
    end
  end

  # @API Show new AI experience form
  #
  # Display the form for creating a new AI experience
  def new
    @experience = @context.ai_experiences.build
    @experience.workflow_state = "unpublished"
    js_env({ COURSE_ID: @context.id })
  end

  # @API Show edit AI experience form
  #
  # Display the form for editing an existing AI experience
  def edit
    js_env({ COURSE_ID: @context.id, AI_EXPERIENCE_ID: params[:id] })
  end

  # @API Create an AI experience
  #
  # Create a new AI experience for the specified course
  #
  # @argument title [Required, String]
  #   The title of the AI experience.
  # @argument description [Optional, String]
  #   The description of the AI experience.
  # @argument facts [Required, String]
  #   The AI facts for the experience.
  # @argument learning_objective [Optional, String]
  #   The learning objectives for this experience.
  # @argument scenario [Optional, String]
  #   The scenario description for the experience.
  # @argument workflow_state [Optional, String]
  #   The initial state of the experience. Defaults to 'unpublished'.
  #   Allowed values: published, unpublished
  #
  # @returns AiExperience
  def create
    @experience = @context.ai_experiences.build(experience_params)
    @experience.root_account = @context.root_account
    @experience.account = @context.account

    if @experience.save
      respond_to do |format|
        format.json { render json: ai_experience_json(@experience, @current_user, session), status: :created }
      end
    else
      respond_to do |format|
        format.json { render json: @experience.errors, status: :bad_request }
      end
    end
  end

  # @API Update an AI experience
  #
  # Update an existing AI experience
  #
  # @argument title [Optional, String]
  #   The title of the AI experience.
  # @argument description [Optional, String]
  #   The description of the AI experience.
  # @argument facts [Optional, String]
  #   The AI facts for the experience.
  # @argument learning_objective [Optional, String]
  #   The learning objectives for this experience.
  # @argument scenario [Optional, String]
  #   The scenario description for the experience.
  # @argument workflow_state [Optional, String]
  #   The state of the experience.
  #   Allowed values: published, unpublished
  #
  # @returns AiExperience
  def update
    if @experience.update(experience_params)
      respond_to do |format|
        format.json { render json: ai_experience_json(@experience, @current_user, session), status: :ok }
      end
    else
      respond_to do |format|
        format.json { render json: @experience.errors, status: :bad_request }
      end
    end
  end

  # @API Delete an AI experience
  #
  # Delete an AI experience (soft delete - marks as deleted)
  #
  # @returns AiExperience
  def destroy
    if @experience.delete
      respond_to do |format|
        format.json { render json: ai_experience_json(@experience, @current_user, session), status: :ok }
      end
    else
      respond_to do |format|
        format.json { render json: @experience.errors, status: :bad_request }
      end
    end
  end

  # @API Continue AI conversation
  #
  # Initialize or continue a conversation with the AI experience
  #
  # @argument messages [Optional, Array]
  #   The conversation history. If empty, will return starting messages.
  #
  # @returns {Array} Array of conversation messages
  def continue_conversation
    service = LLMConversationService.new(
      current_user: @current_user,
      root_account_uuid: @context.root_account.uuid,
      facts: @experience.facts,
      learning_objectives: @experience.learning_objective,
      scenario: @experience.scenario
    )

    messages = if params[:messages].present?
                 service.continue_conversation(
                   messages: params[:messages],
                   new_user_message: params[:new_user_message]
                 )
               else
                 service.starting_messages
               end

    render json: { messages: }
  rescue CedarAi::Errors::ConversationError => e
    render json: { error: e.message }, status: :service_unavailable
  end

  private

  def check_ai_experiences_feature_flag
    unless @context&.feature_enabled?(:ai_experiences)
      render_404
      false
    end
  end

  def load_experience
    @experience = AiExperience.find_by(id: params[:id])
    return render_404 unless @experience&.course == @context && !@experience.deleted?

    nil unless authorized_action(@context, @current_user, :read)
  end

  def require_read_rights
    render_unauthorized_action unless authorized_action(@context, @current_user, :read)
  end

  def require_manage_rights
    permissions = %i[manage_assignments_add manage_assignments_edit manage_assignments_delete]
    unless @context.grants_any_right?(@current_user, *permissions)
      render_unauthorized_action
      false
    end
  end

  def experience_params
    params.require(:ai_experience).permit(:title, :description, :facts, :learning_objective, :scenario, :workflow_state)
  end

  def render_404
    respond_to do |format|
      format.html { render file: Rails.public_path.join("404.html").to_s, status: :not_found, layout: false }
      format.json { render json: { error: "Resource Not Found" }, status: :not_found }
    end
  end
end
