package chatter

import (
	"context"
	"encoding/json"
	"fmt"
	"reflect"

	"github.com/ollama/ollama/api"
)

// OllamaAdapter implements the Adapter interface for local Ollama instances.
type OllamaAdapter struct {
	client *api.Client
	model  string
}

// NewOllamaAdapter initializes a new Ollama client.
// Default address is usually "http://localhost:11434"
func NewOllamaAdapter(endpoint string, model string) (*OllamaAdapter, error) {
	client, err := api.ClientFromEnvironment()
	if err != nil {
		return nil, fmt.Errorf("failed to create ollama client: %w", err)
	}

	return &OllamaAdapter{
		client: client,
		model:  model,
	}, nil
}

// Generate sends the chat history to Ollama and returns the assistant's response.
func (a *OllamaAdapter) Generate(ctx context.Context, messages []Message) (string, error) {
	var ollamaMessages []api.Message

	// Map our internal Message struct to Ollama's API struct
	for _, m := range messages {
		ollamaMessages = append(ollamaMessages, api.Message{
			Role:    string(m.Role),
			Content: m.Content,
		})
	}

	req := &api.ChatRequest{
		Model:    a.model,
		Messages: ollamaMessages,
		Stream:   new(bool), // Set to false for a single string response
	}

	var response string
	err := a.client.Chat(ctx, req, func(resp api.ChatResponse) error {
		fmt.Print(resp.Message)
		response = resp.Message.Content
		return nil
	})
	if err != nil {
		return "", fmt.Errorf("ollama generation failed: %w", err)
	}

	return response, nil
}

func (a *OllamaAdapter) GetProviderName() string {
	return "Ollama (" + a.model + ")"
}

// GenerateStructured implements the same signature as your Gemini adapter.
func (a *OllamaAdapter) GenerateStructured(ctx context.Context, messages []Message, target any) error {
	val := reflect.ValueOf(target)
	if val.Kind() != reflect.Ptr || val.Elem().Kind() != reflect.Struct {
		return fmt.Errorf("target must be a pointer to a struct")
	}

	// 1. Generate the JSON Schema from the Go struct
	schema := a.schemaFromType(val.Elem().Type())
	schemaBytes, err := json.Marshal(schema)
	if err != nil {
		return fmt.Errorf("failed to marshal schema: %w", err)
	}

	// 2. Map messages
	var ollamaMessages []api.Message
	for _, m := range messages {
		ollamaMessages = append(ollamaMessages, api.Message{
			Role:    string(m.Role),
			Content: m.Content,
		})
	}

	// 3. Set Format to the raw JSON Schema
	req := &api.ChatRequest{
		Model:    a.model,
		Messages: ollamaMessages,
		Format:   json.RawMessage(schemaBytes),
		Stream:   new(bool), // false
		Options: map[string]interface{}{
			"temperature": 0, // Recommended for structured tasks
		},
	}

	var responseText string
	err = a.client.Chat(ctx, req, func(resp api.ChatResponse) error {
		responseText = resp.Message.Content
		return nil
	})
	if err != nil {
		return err
	}

	// 4. Parse the result into the target struct
	return json.Unmarshal([]byte(responseText), target)
}

// schemaFromType (Reuse the same logic from the Gemini adapter)
func (a *OllamaAdapter) schemaFromType(t reflect.Type) map[string]interface{} {
	for t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	switch t.Kind() {
	case reflect.Struct:
		props := make(map[string]interface{})
		var required []string

		for i := 0; i < t.NumField(); i++ {
			field := t.Field(i)
			if !field.IsExported() {
				continue
			}
			name := field.Tag.Get("json")
			if name == "" || name == "-" {
				name = field.Name
			}
			props[name] = a.schemaFromType(field.Type)
			required = append(required, name)
		}
		return map[string]interface{}{
			"type":       "object",
			"properties": props,
			"required":   required,
		}

	case reflect.Slice, reflect.Array:
		return map[string]interface{}{
			"type":  "array",
			"items": a.schemaFromType(t.Elem()),
		}

	case reflect.Int, reflect.Int64:
		return map[string]interface{}{"type": "integer"}
	case reflect.Float64:
		return map[string]interface{}{"type": "number"}
	case reflect.Bool:
		return map[string]interface{}{"type": "boolean"}
	default:
		return map[string]interface{}{"type": "string"}
	}
}
