//! Core data structures shared across the server.
//!
//! This file intentionally mixes database row types, HTTP payloads, WebSocket
//! payloads, and a few helper functions so the rest of the codebase can import
//! common shapes from one place.

use serde::{Deserialize, Serialize};

// ── Database models ──

/// Row from the `users` table.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct User {
    pub id: String,
    pub email: String,
    pub display_name: String,
    pub password_hash: String,
    pub created_at: String,
}

/// Row from the `rooms` table.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Room {
    pub id: String,
    pub name: String,
    pub model_id: String,
    pub created_by: String,
    pub ai_always_respond: bool,
    pub system_prompt: String,
    pub ai_name: String,
    pub created_at: String,
    pub deleted_at: Option<String>,
}

/// Row from the `messages` table.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Message {
    pub id: String,
    pub room_id: String,
    pub sender_id: String,
    pub sender_name: String,
    pub content: String,
    pub mentions: String,
    pub is_ai: bool,
    pub created_at: String,
    pub ai_meta: Option<String>,
    pub hash: Option<String>,
}

/// Row from the `invites` table.
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Invite {
    pub id: String,
    pub room_id: String,
    pub invited_by: String,
    pub email: String,
    pub token: String,
    pub used: bool,
    pub created_at: String,
}

// ── API request/response types ──

/// JSON body expected by the registration endpoint.
#[derive(Debug, Deserialize)]
pub struct RegisterRequest {
    pub email: String,
    pub password: String,
    pub display_name: String,
}

/// JSON body expected by the login endpoint.
#[derive(Debug, Deserialize)]
pub struct LoginRequest {
    pub email: String,
    pub password: String,
}

/// Standard auth response returned after login, registration, or profile update.
#[derive(Debug, Serialize)]
pub struct AuthResponse {
    pub token: String,
    pub user: UserPublic,
}

/// Public user data safe to return to any authenticated client.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UserPublic {
    pub id: String,
    pub email: String,
    pub display_name: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub avatar_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub nostr_pubkey: Option<String>,
}

/// JSON body used when a user creates a new chat room.
#[derive(Debug, Deserialize)]
pub struct CreateRoomRequest {
    pub name: String,
    pub model_id: String,
    #[serde(default)]
    pub ai_always_respond: bool,
    #[serde(default = "default_system_prompt")]
    pub system_prompt: String,
    #[serde(default = "default_ai_name")]
    pub ai_name: String,
}

/// Pick a friendly default AI display name when the creator does not specify one.
fn default_ai_name() -> String {
    let names = [
        "Nova", "Atlas", "Sage", "Echo", "Pixel", "Cosmo", "Ember", "Flux", "Lyra", "Onyx",
    ];
    let idx = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as usize % names.len())
        .unwrap_or(0);
    names[idx].to_string()
}

/// Default prompt that defines the AI assistant's behavior inside a room.
fn default_system_prompt() -> String {
    "You are a helpful AI assistant participating in a group chat. Be conversational, helpful, and concise. You can see messages from all participants. When mentioned with @ai, respond helpfully.\n\nYou have access to tools:\n- **web_search**: Search the web for current information. Use this when asked about recent events, news, facts you're unsure about, or anything that needs up-to-date information.\n- **web_fetch**: Fetch and read the content of a web page. Use this when a user shares a URL and wants you to read/summarize it, or when you need more details from a search result.\n\nUse tools proactively when they would help answer the question better. You don't need to ask permission to use them.".to_string()
}

/// Full room payload returned to the client, including current members.
#[derive(Debug, Serialize)]
pub struct RoomResponse {
    pub id: String,
    pub name: String,
    pub model_id: String,
    pub created_by: String,
    pub ai_always_respond: bool,
    pub system_prompt: String,
    pub ai_name: String,
    pub created_at: String,
    pub members: Vec<UserPublic>,
}

/// JSON body for an email-based room invite.
#[derive(Debug, Deserialize)]
pub struct CreateInviteRequest {
    pub room_id: String,
    pub email: String,
}

// ── WebSocket event types ──

/// Messages the browser can send over the WebSocket connection.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum WsClientMessage {
    #[serde(rename = "send_message")]
    SendMessage {
        room_id: String,
        content: String,
        #[serde(default)]
        mentions: Vec<String>,
        #[serde(default)]
        image_url: Option<String>,
    },
    #[serde(rename = "join_room")]
    JoinRoom { room_id: String },
    #[serde(rename = "typing")]
    Typing { room_id: String },
}

/// Messages the server can push to browsers over the WebSocket connection.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum WsServerMessage {
    #[serde(rename = "new_message")]
    NewMessage { message: MessagePayload },
    #[serde(rename = "ai_typing")]
    AiTyping { room_id: String },
    #[serde(rename = "user_typing")]
    UserTyping {
        room_id: String,
        user_id: String,
        display_name: String,
    },
    #[serde(rename = "error")]
    Error { message: String },
    #[serde(rename = "joined")]
    Joined { room_id: String },
    #[serde(rename = "room_deleted")]
    RoomDeleted { room_id: String },
    #[serde(rename = "room_cleared")]
    RoomCleared { room_id: String },
    #[serde(rename = "ai_tool_usage")]
    AiToolUsage {
        room_id: String,
        tool_name: String,
        status: String,
    },
    #[serde(rename = "ai_stream_chunk")]
    AiStreamChunk {
        room_id: String,
        message_id: String,
        delta: String,
    },
    #[serde(rename = "ai_stream_end")]
    AiStreamEnd { room_id: String, message_id: String },
}

/// Message shape sent to clients for history loading and live updates.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MessagePayload {
    pub id: String,
    pub room_id: String,
    pub sender_id: String,
    pub sender_name: String,
    pub content: String,
    pub mentions: Vec<String>,
    pub is_ai: bool,
    pub created_at: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub ai_meta: Option<AiMeta>,
    #[serde(default)]
    pub avatar_hash: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub avatar_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub image_url: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub hash: Option<String>,
}

/// Compute Gravatar-compatible MD5 hash from an email address.
pub fn gravatar_hash(email: &str) -> String {
    use md5::{Digest, Md5};
    let normalized = email.trim().to_lowercase();
    let result = Md5::digest(normalized.as_bytes());
    format!("{:x}", result)
}

/// Compute SHA-256 integrity hash from created_at timestamp + message content.
pub fn message_hash(created_at: &str, content: &str) -> String {
    use sha2::{Digest, Sha256};
    let mut hasher = Sha256::new();
    hasher.update(created_at.as_bytes());
    hasher.update(content.as_bytes());
    format!("{:x}", hasher.finalize())
}

/// Usage and tool metadata captured for AI-generated messages.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct AiMeta {
    pub model: String,
    pub prompt_tokens: u32,
    pub completion_tokens: u32,
    pub total_tokens: u32,
    pub response_ms: u64,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub tool_results: Option<Vec<ToolResult>>,
}

/// One tool invocation performed while generating an AI answer.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolResult {
    pub tool: String,
    pub input: String,
    pub result: String,
}

// ── Broadcast event (internal channel) ──

/// Internal event sent through a Tokio broadcast channel to WebSocket tasks.
#[derive(Debug, Clone)]
pub struct BroadcastEvent {
    pub room_id: String,
    pub message: WsServerMessage,
}

// ── JWT Claims ──

/// Claims stored inside the server-issued JWT.
#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String, // user_id
    pub email: String,
    pub display_name: String,
    pub exp: usize,
}

// ── Pagination ──

/// Common pagination parameters for message history endpoints.
#[derive(Debug, Deserialize)]
pub struct PaginationParams {
    #[serde(default = "default_limit")]
    pub limit: i64,
    pub before: Option<String>,
}

fn default_limit() -> i64 {
    50
}

/// Hide placeholder `nostr:*` emails from normal client responses.
pub fn public_email(email: &str) -> String {
    if email.starts_with("nostr:") {
        String::new()
    } else {
        email.to_string()
    }
}

// ── Nostr auth types ──

/// Response returned by the Nostr challenge endpoint.
#[derive(Debug, Serialize)]
pub struct NostrChallengeResponse {
    pub challenge: String,
}

/// JSON body sent by the client when proving Nostr ownership.
#[derive(Debug, Deserialize)]
pub struct NostrVerifyRequest {
    pub signed_event: String,
    pub challenge: String,
    pub profile_name: Option<String>,
    pub profile_picture: Option<String>,
}

/// JSON body for inviting an already-known Nostr user into a room.
#[derive(Debug, Deserialize)]
pub struct NostrInviteRequest {
    pub room_id: String,
    pub nostr_pubkey: String,
}
