//! Discussion endpoint for LLM-powered contract creation.
//!
//! This handler provides an ephemeral conversation with Makima to help users
//! define and create contracts through natural dialogue.

use axum::{
    extract::State,
    http::StatusCode,
    response::IntoResponse,
    Json,
};
use serde::{Deserialize, Serialize};
use serde_json::json;
use utoipa::ToSchema;
use uuid::Uuid;

use crate::db::{models::CreateContractRequest, repository};
use crate::llm::{
    claude::{self, ClaudeClient, ClaudeError, ClaudeModel},
    groq::{GroqClient, GroqError, Message, ToolCallResponse},
    discuss_tools::{parse_discuss_tool_call, DiscussToolRequest, DISCUSS_TOOLS},
    LlmModel, ToolCall, ToolResult, UserQuestion,
};
use crate::server::auth::Authenticated;
use crate::server::state::SharedState;

/// Maximum number of tool-calling rounds to prevent infinite loops
const MAX_TOOL_ROUNDS: usize = 10;

/// System prompt for Makima character in contract discussions
const DISCUSS_SYSTEM_PROMPT: &str = r#"
You are Makima, an AI assistant on the makima.jp platform. You help users define and create contracts for their projects through natural conversation.

## Your Personality
- Professional yet personable
- Focused on understanding the user's actual needs
- Ask clarifying questions when requirements are vague
- Guide the conversation toward actionable outcomes
- Comfortable making recommendations based on experience

## Your Goal
Help the user flesh out their project idea into a well-defined contract. A contract on makima.jp includes:
- A clear name and description
- The right contract type (simple, specification, or execute)
- Understanding of the scope and requirements

## Contract Types
- **simple**: Quick tasks with minimal planning (plan -> execute phases only)
- **specification**: Full lifecycle projects (research -> specify -> plan -> execute -> review)
- **execute**: Direct implementation when requirements are already clear (execute phase only)

## Guidelines
1. **Start by understanding**: Ask about what they want to build
2. **Clarify scope**: Is this a quick fix, a new feature, or a full project?
3. **Gather requirements**: What are the must-haves vs nice-to-haves?
4. **Identify context**: Is there existing code? Which repository?
5. **Recommend type**: Suggest the appropriate contract type
6. **Confirm and create**: When the user is satisfied, create the contract

## When to Create the Contract
Create the contract when:
- You have a clear understanding of what the user wants
- The user has confirmed they're ready to proceed
- You've gathered enough information for a meaningful contract

Do NOT create the contract if:
- The user is still exploring ideas
- Key information is missing
- The user hasn't indicated readiness

{transcript_context}
"#;

/// Chat message in history
#[derive(Debug, Clone, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ChatMessage {
    /// Role: "user" or "assistant"
    pub role: String,
    /// Message content
    pub content: String,
}

/// Request to discuss a potential contract
#[derive(Debug, Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DiscussContractRequest {
    /// The user's message
    pub message: String,
    /// Optional model selection (default: claude-sonnet)
    #[serde(default)]
    pub model: Option<String>,
    /// Conversation history for context continuity
    #[serde(default)]
    pub history: Option<Vec<ChatMessage>>,
    /// Optional transcript context from current session
    #[serde(default)]
    pub transcript_context: Option<String>,
}

/// Response from the discussion endpoint
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct DiscussContractResponse {
    /// Makima's response message
    pub response: String,
    /// Tool calls that were executed (e.g., create_contract)
    pub tool_calls: Vec<ToolCallInfo>,
    /// If a contract was created, its details
    #[serde(skip_serializing_if = "Option::is_none")]
    pub created_contract: Option<CreatedContractInfo>,
    /// Pending questions (if LLM needs clarification)
    #[serde(skip_serializing_if = "Option::is_none")]
    pub pending_questions: Option<Vec<UserQuestion>>,
}

/// Information about a tool call that was executed
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct ToolCallInfo {
    pub name: String,
    pub result: ToolResult,
}

/// Information about a created contract
#[derive(Debug, Serialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub struct CreatedContractInfo {
    pub id: String,
    pub name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub description: Option<String>,
    pub contract_type: String,
    pub initial_phase: String,
}

/// Enum to hold LLM clients
enum LlmClient {
    Groq(GroqClient),
    Claude(ClaudeClient),
}

/// Unified result from LLM call
struct LlmResult {
    content: Option<String>,
    tool_calls: Vec<ToolCall>,
    raw_tool_calls: Vec<ToolCallResponse>,
    finish_reason: String,
}

/// Discuss a potential contract with Makima
#[utoipa::path(
    post,
    path = "/api/v1/contracts/discuss",
    request_body = DiscussContractRequest,
    responses(
        (status = 200, description = "Discussion completed successfully", body = DiscussContractResponse),
        (status = 401, description = "Unauthorized"),
        (status = 500, description = "Internal server error")
    ),
    security(
        ("bearer_auth" = []),
        ("api_key" = [])
    ),
    tag = "Contracts"
)]
pub async fn discuss_contract_handler(
    State(state): State<SharedState>,
    Authenticated(auth): Authenticated,
    Json(request): Json<DiscussContractRequest>,
) -> impl IntoResponse {
    // Check if database is configured
    let Some(ref pool) = state.db_pool else {
        return (
            StatusCode::SERVICE_UNAVAILABLE,
            Json(json!({ "error": "Database not configured" })),
        )
            .into_response();
    };

    // Parse model selection (default to Claude Sonnet)
    let model = request
        .model
        .as_ref()
        .and_then(|m| LlmModel::from_str(m))
        .unwrap_or(LlmModel::ClaudeSonnet);

    tracing::info!("Contract discussion using LLM model: {:?}", model);

    // Initialize the appropriate LLM client
    let llm_client = match model {
        LlmModel::ClaudeSonnet => match ClaudeClient::from_env(ClaudeModel::Sonnet) {
            Ok(client) => LlmClient::Claude(client),
            Err(ClaudeError::MissingApiKey) => {
                return (
                    StatusCode::SERVICE_UNAVAILABLE,
                    Json(json!({ "error": "ANTHROPIC_API_KEY not configured" })),
                )
                    .into_response();
            }
            Err(e) => {
                return (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(json!({ "error": format!("Claude client error: {}", e) })),
                )
                    .into_response();
            }
        },
        LlmModel::ClaudeOpus => match ClaudeClient::from_env(ClaudeModel::Opus) {
            Ok(client) => LlmClient::Claude(client),
            Err(ClaudeError::MissingApiKey) => {
                return (
                    StatusCode::SERVICE_UNAVAILABLE,
                    Json(json!({ "error": "ANTHROPIC_API_KEY not configured" })),
                )
                    .into_response();
            }
            Err(e) => {
                return (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(json!({ "error": format!("Claude client error: {}", e) })),
                )
                    .into_response();
            }
        },
        LlmModel::GroqKimi => match GroqClient::from_env() {
            Ok(client) => LlmClient::Groq(client),
            Err(GroqError::MissingApiKey) => {
                return (
                    StatusCode::SERVICE_UNAVAILABLE,
                    Json(json!({ "error": "GROQ_API_KEY not configured" })),
                )
                    .into_response();
            }
            Err(e) => {
                return (
                    StatusCode::INTERNAL_SERVER_ERROR,
                    Json(json!({ "error": format!("Groq client error: {}", e) })),
                )
                    .into_response();
            }
        },
    };

    // Build system prompt with optional transcript context
    let transcript_section = match &request.transcript_context {
        Some(ctx) => format!(
            "\n## Current Session Context\nThe user has been recording a session. Here's the transcript:\n\n{}\n",
            ctx
        ),
        None => String::new(),
    };

    let system_prompt = DISCUSS_SYSTEM_PROMPT.replace("{transcript_context}", &transcript_section);

    // Run the discussion agentic loop
    run_discuss_agentic_loop(
        pool,
        &llm_client,
        system_prompt,
        &request,
        auth.owner_id,
    )
    .await
}

/// Run the agentic loop for contract discussion
async fn run_discuss_agentic_loop(
    pool: &sqlx::PgPool,
    llm_client: &LlmClient,
    system_prompt: String,
    request: &DiscussContractRequest,
    owner_id: Uuid,
) -> axum::response::Response {
    // Build initial messages
    let mut messages = vec![Message {
        role: "system".to_string(),
        content: Some(system_prompt),
        tool_calls: None,
        tool_call_id: None,
    }];

    // Add conversation history if provided
    if let Some(history) = &request.history {
        for msg in history {
            messages.push(Message {
                role: msg.role.clone(),
                content: Some(msg.content.clone()),
                tool_calls: None,
                tool_call_id: None,
            });
        }
    }

    // Add current user message
    messages.push(Message {
        role: "user".to_string(),
        content: Some(request.message.clone()),
        tool_calls: None,
        tool_call_id: None,
    });

    // State for tracking
    let mut all_tool_call_infos: Vec<ToolCallInfo> = Vec::new();
    let mut final_response: Option<String> = None;
    let mut created_contract: Option<CreatedContractInfo> = None;
    let mut pending_questions: Option<Vec<UserQuestion>> = None;

    // Multi-turn agentic tool calling loop
    for round in 0..MAX_TOOL_ROUNDS {
        tracing::info!(
            round = round,
            total_tool_calls = all_tool_call_infos.len(),
            "Contract discussion loop iteration"
        );

        // Call the appropriate LLM API
        let result = match llm_client {
            LlmClient::Groq(groq) => {
                match groq.chat_with_tools(messages.clone(), &DISCUSS_TOOLS).await {
                    Ok(r) => LlmResult {
                        content: r.content,
                        tool_calls: r.tool_calls,
                        raw_tool_calls: r.raw_tool_calls,
                        finish_reason: r.finish_reason,
                    },
                    Err(e) => {
                        tracing::error!("Groq API error: {}", e);
                        return (
                            StatusCode::INTERNAL_SERVER_ERROR,
                            Json(json!({ "error": format!("LLM API error: {}", e) })),
                        )
                            .into_response();
                    }
                }
            }
            LlmClient::Claude(claude_client) => {
                let claude_messages = claude::groq_messages_to_claude(&messages);
                match claude_client
                    .chat_with_tools(claude_messages, &DISCUSS_TOOLS)
                    .await
                {
                    Ok(r) => {
                        let raw_tool_calls: Vec<ToolCallResponse> = r
                            .tool_calls
                            .iter()
                            .map(|tc| ToolCallResponse {
                                id: tc.id.clone(),
                                call_type: "function".to_string(),
                                function: crate::llm::groq::FunctionCall {
                                    name: tc.name.clone(),
                                    arguments: tc.arguments.to_string(),
                                },
                            })
                            .collect();

                        LlmResult {
                            content: r.content,
                            tool_calls: r.tool_calls,
                            raw_tool_calls,
                            finish_reason: r.stop_reason,
                        }
                    }
                    Err(e) => {
                        tracing::error!("Claude API error: {}", e);
                        return (
                            StatusCode::INTERNAL_SERVER_ERROR,
                            Json(json!({ "error": format!("LLM API error: {}", e) })),
                        )
                            .into_response();
                    }
                }
            }
        };

        // Check if there are tool calls to execute
        if result.tool_calls.is_empty() {
            final_response = result.content;
            break;
        }

        // Add assistant message with tool calls to conversation
        messages.push(Message {
            role: "assistant".to_string(),
            content: result.content.clone(),
            tool_calls: Some(result.raw_tool_calls.clone()),
            tool_call_id: None,
        });

        // Execute each tool call
        for (i, tool_call) in result.tool_calls.iter().enumerate() {
            tracing::info!(tool = %tool_call.name, round = round, "Executing discussion tool call");

            // Parse the tool call
            let mut execution_result = parse_discuss_tool_call(tool_call);

            // Handle async discussion tool requests
            if let Some(discuss_request) = execution_result.request.take() {
                let async_result =
                    handle_discuss_request(pool, discuss_request, owner_id).await;
                execution_result.success = async_result.success;
                execution_result.message = async_result.message;
                execution_result.data = async_result.data;

                // Check if a contract was created
                if let Some(ref data) = execution_result.data {
                    if let Some(contract_info) = data.get("createdContract") {
                        created_contract = Some(CreatedContractInfo {
                            id: contract_info["id"].as_str().unwrap_or("").to_string(),
                            name: contract_info["name"].as_str().unwrap_or("").to_string(),
                            description: contract_info["description"].as_str().map(|s| s.to_string()),
                            contract_type: contract_info["contractType"].as_str().unwrap_or("").to_string(),
                            initial_phase: contract_info["initialPhase"].as_str().unwrap_or("").to_string(),
                        });
                    }
                }
            }

            // Check for pending user questions
            if let Some(questions) = execution_result.pending_questions {
                tracing::info!(
                    question_count = questions.len(),
                    "Discussion LLM requesting user input"
                );
                pending_questions = Some(questions);
                all_tool_call_infos.push(ToolCallInfo {
                    name: tool_call.name.clone(),
                    result: ToolResult {
                        success: execution_result.success,
                        message: execution_result.message.clone(),
                    },
                });
                break;
            }

            // Build tool result message
            let result_content = if let Some(data) = &execution_result.data {
                json!({
                    "success": execution_result.success,
                    "message": execution_result.message,
                    "data": data
                })
                .to_string()
            } else {
                json!({
                    "success": execution_result.success,
                    "message": execution_result.message
                })
                .to_string()
            };

            // Add tool result message
            let tool_call_id = match llm_client {
                LlmClient::Groq(_) => result.raw_tool_calls[i].id.clone(),
                LlmClient::Claude(_) => tool_call.id.clone(),
            };

            messages.push(Message {
                role: "tool".to_string(),
                content: Some(result_content),
                tool_calls: None,
                tool_call_id: Some(tool_call_id),
            });

            // Track for response
            all_tool_call_infos.push(ToolCallInfo {
                name: tool_call.name.clone(),
                result: ToolResult {
                    success: execution_result.success,
                    message: execution_result.message,
                },
            });
        }

        // If user questions are pending, pause
        if pending_questions.is_some() {
            final_response = result.content;
            break;
        }

        // If finish reason indicates completion, exit loop
        let finish_lower = result.finish_reason.to_lowercase();
        if finish_lower == "stop" || finish_lower == "end_turn" {
            final_response = result.content;
            break;
        }
    }

    // Build response
    let response_text = final_response.unwrap_or_else(|| {
        if all_tool_call_infos.is_empty() {
            "I couldn't understand your request. Please try rephrasing.".to_string()
        } else {
            "Done!".to_string()
        }
    });

    (
        StatusCode::OK,
        Json(DiscussContractResponse {
            response: response_text,
            tool_calls: all_tool_call_infos,
            created_contract,
            pending_questions,
        }),
    )
        .into_response()
}

/// Result from handling an async discussion tool request
struct DiscussRequestResult {
    success: bool,
    message: String,
    data: Option<serde_json::Value>,
}

/// Handle async discussion tool requests that require database access
async fn handle_discuss_request(
    pool: &sqlx::PgPool,
    request: DiscussToolRequest,
    owner_id: Uuid,
) -> DiscussRequestResult {
    match request {
        DiscussToolRequest::CreateContract {
            name,
            description,
            contract_type,
            repository_url,
            local_only,
        } => {
            // Create the contract request
            let create_req = CreateContractRequest {
                name: name.clone(),
                description: Some(description.clone()),
                contract_type: Some(contract_type.clone()),
                template_id: None,
                initial_phase: None,
                autonomous_loop: None,
                phase_guard: None,
                local_only: Some(local_only),
                auto_merge_local: None,
            };

            match repository::create_contract_for_owner(pool, owner_id, create_req).await {
                Ok(contract) => {
                    // If repository URL was provided, try to add it
                    if let Some(repo_url) = repository_url {
                        // Try to add as remote repository
                        let add_result = repository::add_remote_repository(
                            pool,
                            contract.id,
                            &format!("{} Repository", name),
                            &repo_url,
                            true, // is_primary
                        )
                        .await;

                        if let Err(e) = add_result {
                            tracing::warn!(
                                "Failed to add repository to contract {}: {}",
                                contract.id,
                                e
                            );
                        }
                    }

                    DiscussRequestResult {
                        success: true,
                        message: format!("Contract '{}' created successfully!", contract.name),
                        data: Some(json!({
                            "createdContract": {
                                "id": contract.id.to_string(),
                                "name": contract.name,
                                "description": contract.description,
                                "contractType": contract.contract_type,
                                "initialPhase": contract.phase,
                            }
                        })),
                    }
                }
                Err(e) => {
                    tracing::error!("Failed to create contract: {}", e);
                    DiscussRequestResult {
                        success: false,
                        message: format!("Failed to create contract: {}", e),
                        data: None,
                    }
                }
            }
        }
    }
}
