package ollama

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

const defaultTimeout = 10 * time.Minute

// Client calls Ollama /api/chat.
type Client struct {
	BaseURL    string
	HTTPClient *http.Client
}

// NewClient creates an Ollama client. baseURL is e.g. "http://localhost:11434".
func NewClient(baseURL string) *Client {
	return &Client{
		BaseURL: baseURL,
		HTTPClient: &http.Client{
			Timeout: defaultTimeout,
		},
	}
}

// ChatRequest matches Ollama POST /api/chat body.
type ChatRequest struct {
	Model    string        `json:"model"`
	Messages []ChatMessage `json:"messages"`
	Stream   bool          `json:"stream"`
	Format   interface{}   `json:"format,omitempty"` // "json" or JSON schema object
	Tools    []Tool        `json:"tools,omitempty"`
}

// ChatMessage is one message in the conversation.
type ChatMessage struct {
	Role       string        `json:"role"` // "user", "assistant", "system", "tool"
	Content    string        `json:"content,omitempty"`
	ToolCalls  []ToolCall    `json:"tool_calls,omitempty"`
	ToolName   string        `json:"tool_name,omitempty"` // for role "tool"
}

// Tool defines a function the model may call.
type Tool struct {
	Type     string   `json:"type"`
	Function ToolFunc `json:"function"`
}

// ToolFunc describes the function.
type ToolFunc struct {
	Name        string      `json:"name"`
	Description string      `json:"description"`
	Parameters  interface{}  `json:"parameters"`
}

// ToolCall is a model request to call a tool.
type ToolCall struct {
	Type     string     `json:"type"`
	Function ToolCallFn `json:"function"`
}

// ToolCallFn holds name and arguments.
// Arguments may come from Ollama as a JSON object or as a JSON string.
type ToolCallFn struct {
	Name      string      `json:"name"`
	Arguments interface{} `json:"arguments"` // object or string
}

// QueryFromToolCall returns the "query" argument from a web_search tool call.
// Ollama may send arguments as a map or as a JSON string.
func QueryFromToolCall(tc ToolCall) string {
	switch v := tc.Function.Arguments.(type) {
	case map[string]interface{}:
		if q, _ := v["query"].(string); q != "" {
			return q
		}
	case string:
		var m map[string]interface{}
		if json.Unmarshal([]byte(v), &m) == nil {
			if q, _ := m["query"].(string); q != "" {
				return q
			}
		}
	}
	return ""
}

// ChatResponse is the Ollama /api/chat response.
type ChatResponse struct {
	Message ChatMessage `json:"message"`
	Done    bool        `json:"done"`
}

// Chat sends a chat request and returns the response.
func (c *Client) Chat(req *ChatRequest) (*ChatResponse, error) {
	body, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("marshal request: %w", err)
	}
	url := c.BaseURL + "/api/chat"
	httpReq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))
	if err != nil {
		return nil, fmt.Errorf("new request: %w", err)
	}
	httpReq.Header.Set("Content-Type", "application/json")

	resp, err := c.HTTPClient.Do(httpReq)
	if err != nil {
		return nil, fmt.Errorf("do request: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		b, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("ollama returned %d: %s", resp.StatusCode, string(b))
	}

	var out ChatResponse
	if err := json.NewDecoder(resp.Body).Decode(&out); err != nil {
		return nil, fmt.Errorf("decode response: %w", err)
	}
	return &out, nil
}

// WebSearchTool returns the tool definition for web_search (Tavily).
func WebSearchTool() Tool {
	return Tool{
		Type: "function",
		Function: ToolFunc{
			Name:        "web_search",
			Description: "Search the web for current information. Use when you need up-to-date or factual information from the internet.",
			Parameters: map[string]interface{}{
				"type": "object",
				"properties": map[string]interface{}{
					"query": map[string]interface{}{
						"type":        "string",
						"description": "Search query",
					},
				},
				"required": []string{"query"},
			},
		},
	}
}
