// Package llm contains impure LLM provider implementations.
package llm

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"os"
	"time"

	coretypes "github.com/enmanuel/agents/pkg/llm"
	"github.com/enmanuel/agents/shell/logger"
)

const anthropicAPIBase = "https://api.anthropic.com/v1"
const anthropicVersion = "2023-06-01"

// NewAnthropicComplete returns a CompleteFunc backed by the Anthropic API.
func NewAnthropicComplete(apiKeyEnv, baseURL string, log *slog.Logger) coretypes.CompleteFunc {
	if baseURL == "" {
		baseURL = anthropicAPIBase
	}

	return func(ctx context.Context, req coretypes.CompletionRequest) (coretypes.CompletionResponse, error) {
		apiKey := os.Getenv(apiKeyEnv)
		if apiKey == "" {
			return coretypes.CompletionResponse{}, fmt.Errorf("env var %s is not set", apiKeyEnv)
		}

		log.Info("llm_request",
			"provider", "anthropic",
			"model", req.Model,
			"messages", len(req.Messages),
			"tools", len(req.Tools),
		)

		body := toAnthropicRequest(req)
		raw, err := json.Marshal(body)
		if err != nil {
			return coretypes.CompletionResponse{}, fmt.Errorf("marshal request: %w", err)
		}

		httpReq, err := http.NewRequestWithContext(ctx, http.MethodPost, baseURL+"/messages", bytes.NewReader(raw))
		if err != nil {
			return coretypes.CompletionResponse{}, err
		}
		httpReq.Header.Set("x-api-key", apiKey)
		httpReq.Header.Set("anthropic-version", anthropicVersion)
		httpReq.Header.Set("content-type", "application/json")

		start := time.Now()
		resp, err := http.DefaultClient.Do(httpReq)
		if err != nil {
			ms := time.Since(start).Milliseconds()
			log.Error("llm_error", "provider", "anthropic", logger.FieldDurationMS, ms, "err", err)
			return coretypes.CompletionResponse{}, fmt.Errorf("anthropic request: %w", err)
		}
		defer resp.Body.Close()

		respBytes, err := io.ReadAll(resp.Body)
		if err != nil {
			return coretypes.CompletionResponse{}, fmt.Errorf("read response: %w", err)
		}

		ms := time.Since(start).Milliseconds()
		if resp.StatusCode != http.StatusOK {
			log.Error("llm_error", "provider", "anthropic", logger.FieldDurationMS, ms, "status", resp.StatusCode)
			return coretypes.CompletionResponse{}, fmt.Errorf("anthropic error %d: %s", resp.StatusCode, respBytes)
		}

		result, err := fromAnthropicResponse(respBytes)
		if err != nil {
			log.Error("llm_error", "provider", "anthropic", logger.FieldDurationMS, ms, "err", err)
			return result, err
		}

		log.Info("llm_response",
			"provider", "anthropic",
			"model", req.Model,
			logger.FieldDurationMS, ms,
			logger.FieldTokensUsed, result.Usage.TotalTokens,
			"input_tokens", result.Usage.InputTokens,
			"output_tokens", result.Usage.OutputTokens,
			"tool_calls", len(result.ToolCalls),
			"finish_reason", result.FinishReason,
		)

		return result, nil
	}
}

// ── private conversion helpers ────────────────────────────────────────────

type anthropicRequest struct {
	Model     string             `json:"model"`
	MaxTokens int                `json:"max_tokens"`
	System    string             `json:"system,omitempty"`
	Messages  []anthropicMessage `json:"messages"`
	Tools     []anthropicTool    `json:"tools,omitempty"`
}

type anthropicMessage struct {
	Role    string          `json:"role"`
	Content json.RawMessage `json:"content"`
}

type anthropicTool struct {
	Name        string         `json:"name"`
	Description string         `json:"description"`
	InputSchema map[string]any `json:"input_schema"`
}

// anthropicContentBlock represents a block in a content array.
type anthropicContentBlock struct {
	Type string `json:"type"`

	// text block
	Text string `json:"text,omitempty"`

	// tool_use block (in assistant responses)
	ID    string         `json:"id,omitempty"`
	Name  string         `json:"name,omitempty"`
	Input map[string]any `json:"input,omitempty"`

	// tool_result block (in user messages)
	ToolUseID string `json:"tool_use_id,omitempty"`
	Content   string `json:"content,omitempty"`
}

type anthropicResponse struct {
	Content []anthropicContentBlock `json:"content"`
	Usage   struct {
		InputTokens  int `json:"input_tokens"`
		OutputTokens int `json:"output_tokens"`
	} `json:"usage"`
	StopReason string `json:"stop_reason"`
}

func toAnthropicRequest(req coretypes.CompletionRequest) anthropicRequest {
	msgs := make([]anthropicMessage, 0, len(req.Messages))
	for _, m := range req.Messages {
		if m.Role == coretypes.RoleSystem {
			continue
		}
		msgs = append(msgs, toAnthropicMessage(m))
	}

	tools := make([]anthropicTool, len(req.Tools))
	for i, t := range req.Tools {
		tools[i] = anthropicTool{
			Name:        t.Name,
			Description: t.Description,
			InputSchema: t.InputSchema,
		}
	}

	return anthropicRequest{
		Model:     req.Model,
		MaxTokens: req.MaxTokens,
		System:    req.SystemPrompt,
		Messages:  msgs,
		Tools:     tools,
	}
}

// toAnthropicMessage converts a core Message to the Anthropic format.
// Handles plain text, assistant messages with tool calls, and tool result messages.
func toAnthropicMessage(m coretypes.Message) anthropicMessage {
	// Assistant message with tool calls → content array with text + tool_use blocks
	if m.Role == coretypes.RoleAssistant && len(m.ToolCalls) > 0 {
		blocks := make([]anthropicContentBlock, 0, len(m.ToolCalls)+1)
		if m.Content != "" {
			blocks = append(blocks, anthropicContentBlock{Type: "text", Text: m.Content})
		}
		for _, tc := range m.ToolCalls {
			var input map[string]any
			_ = json.Unmarshal([]byte(tc.Arguments), &input)
			blocks = append(blocks, anthropicContentBlock{
				Type:  "tool_use",
				ID:    tc.ID,
				Name:  tc.Name,
				Input: input,
			})
		}
		raw, _ := json.Marshal(blocks)
		return anthropicMessage{Role: "assistant", Content: raw}
	}

	// Tool result message → user message with tool_result content array
	if m.Role == coretypes.RoleTool {
		blocks := []anthropicContentBlock{{
			Type:      "tool_result",
			ToolUseID: m.ToolCallID,
			Content:   m.Content,
		}}
		raw, _ := json.Marshal(blocks)
		return anthropicMessage{Role: "user", Content: raw}
	}

	// Plain text message
	raw, _ := json.Marshal(m.Content)
	return anthropicMessage{Role: string(m.Role), Content: raw}
}

func fromAnthropicResponse(raw []byte) (coretypes.CompletionResponse, error) {
	var ar anthropicResponse
	if err := json.Unmarshal(raw, &ar); err != nil {
		return coretypes.CompletionResponse{}, fmt.Errorf("unmarshal response: %w", err)
	}

	var content string
	var toolCalls []coretypes.ToolCall

	for _, c := range ar.Content {
		switch c.Type {
		case "text":
			content += c.Text
		case "tool_use":
			argsJSON, _ := json.Marshal(c.Input)
			toolCalls = append(toolCalls, coretypes.ToolCall{
				ID:        c.ID,
				Name:      c.Name,
				Arguments: string(argsJSON),
			})
		}
	}

	return coretypes.CompletionResponse{
		Content:      content,
		ToolCalls:    toolCalls,
		FinishReason: ar.StopReason,
		Usage: coretypes.TokenUsage{
			InputTokens:  ar.Usage.InputTokens,
			OutputTokens: ar.Usage.OutputTokens,
			TotalTokens:  ar.Usage.InputTokens + ar.Usage.OutputTokens,
		},
	}, nil
}
