package anthropic

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

	"github.com/rose_cat707/luminary/internal/model"
	"github.com/rose_cat707/luminary/internal/provider"
)

type Client struct {
	http *provider.HTTPClientWrapper
}

func New() *Client {
	return &Client{http: provider.NewHTTPClientWrapper()}
}

func (c *Client) Type() string { return "anthropic" }

func (c *Client) ListModels(ctx context.Context, apiKey, baseURL string) ([]provider.ModelInfo, error) {
	return c.ListModelsWithConfig(ctx, apiKey, provider.ProviderConfig{
		Type:    model.ProviderAnthropic,
		BaseURL: provider.NormalizeBaseURL(baseURL, model.ProviderAnthropic),
	})
}

func (c *Client) ListModelsWithConfig(ctx context.Context, apiKey string, cfg provider.ProviderConfig) ([]provider.ModelInfo, error) {
	cfg.Type = model.ProviderAnthropic
	cfg.BaseURL = provider.NormalizeBaseURL(cfg.BaseURL, model.ProviderAnthropic)
	url := cfg.ResolveEndpointURL(provider.EndpointListModels)
	resp, err := provider.ForwardHTTP(ctx, c.http.Client, "GET", url, apiKey, model.ProviderAnthropic, nil, nil)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("anthropic list models: status %d: %s", resp.StatusCode, string(resp.Body))
	}
	var out struct {
		Data []struct {
			ID   string `json:"id"`
			Name string `json:"display_name"`
		} `json:"data"`
	}
	if err := json.Unmarshal(resp.Body, &out); err != nil {
		return nil, err
	}
	models := make([]provider.ModelInfo, 0, len(out.Data))
	for _, m := range out.Data {
		name := m.Name
		if name == "" {
			name = m.ID
		}
		models = append(models, provider.ModelInfo{ID: m.ID, DisplayName: name})
	}
	return models, nil
}

func (c *Client) ChatCompletion(ctx context.Context, apiKey, baseURL string, req *provider.ChatRequest) (*provider.ChatResponse, error) {
	cfg := provider.ProviderConfig{
		Type:    model.ProviderAnthropic,
		BaseURL: provider.NormalizeBaseURL(baseURL, model.ProviderAnthropic),
	}
	anthropicReq, err := convertOpenAIToAnthropic(req)
	if err != nil {
		return nil, err
	}
	body, err := json.Marshal(anthropicReq)
	if err != nil {
		return nil, err
	}
	url := cfg.ResolveEndpointURL(provider.EndpointChatCompletion)
	resp, err := provider.ForwardHTTP(ctx, c.http.Client, "POST", url, apiKey, model.ProviderAnthropic, body, nil)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 400 {
		return &provider.ChatResponse{StatusCode: resp.StatusCode, Body: resp.Body}, nil
	}
	openAIResp, tokensIn, tokensOut := convertAnthropicToOpenAI(resp.Body, req.Model)
	return &provider.ChatResponse{
		StatusCode: 200,
		Body:       openAIResp,
		TokensIn:   tokensIn,
		TokensOut:  tokensOut,
	}, nil
}

func (c *Client) ForwardWithConfig(ctx context.Context, apiKey string, cfg provider.ProviderConfig, endpoint provider.EndpointType, body []byte) (*provider.ChatResponse, error) {
	cfg.Type = model.ProviderAnthropic
	cfg.BaseURL = provider.NormalizeBaseURL(cfg.BaseURL, model.ProviderAnthropic)
	if endpoint == provider.EndpointChatCompletion {
		var payload map[string]any
		if err := json.Unmarshal(body, &payload); err == nil {
			modelName, _ := payload["model"].(string)
			anthropicReq, err := convertOpenAIToAnthropic(&provider.ChatRequest{Model: modelName, Raw: body})
			if err == nil {
				body, _ = json.Marshal(anthropicReq)
			}
		}
	}
	url := cfg.ResolveEndpointURL(endpoint)
	resp, err := provider.ForwardHTTP(ctx, c.http.Client, "POST", url, apiKey, model.ProviderAnthropic, body, nil)
	if err != nil {
		return nil, err
	}
	if endpoint == provider.EndpointChatCompletion && resp.StatusCode < 400 {
		var payload map[string]any
		modelName := ""
		_ = json.Unmarshal(body, &payload)
		if m, ok := payload["model"].(string); ok {
			modelName = m
		}
		openAIResp, tokensIn, tokensOut := convertAnthropicToOpenAI(resp.Body, modelName)
		return &provider.ChatResponse{StatusCode: 200, Body: openAIResp, TokensIn: tokensIn, TokensOut: tokensOut}, nil
	}
	return &provider.ChatResponse{StatusCode: resp.StatusCode, Body: resp.Body, TokensIn: resp.TokensIn, TokensOut: resp.TokensOut}, nil
}

func (c *Client) HealthCheck(ctx context.Context, apiKey, baseURL string) error {
	_, err := c.ListModels(ctx, apiKey, baseURL)
	return err
}

func (c *Client) HealthCheckWithConfig(ctx context.Context, apiKey string, cfg provider.ProviderConfig) error {
	_, err := c.ListModelsWithConfig(ctx, apiKey, cfg)
	return err
}

// convertOpenAIToAnthropic and convertAnthropicToOpenAI unchanged below

func convertOpenAIToAnthropic(req *provider.ChatRequest) (map[string]any, error) {
	var payload map[string]any
	raw := req.Raw
	if len(raw) == 0 {
		b, _ := json.Marshal(req)
		raw = b
	}
	if err := json.Unmarshal(raw, &payload); err != nil {
		return nil, err
	}
	model, _ := payload["model"].(string)
	if model == "" {
		model = req.Model
	}
	msgs, _ := payload["messages"].([]any)
	var system string
	var anthropicMsgs []map[string]any
	for _, m := range msgs {
		msg, _ := m.(map[string]any)
		role, _ := msg["role"].(string)
		if role == "system" {
			system = extractOpenAIContent(msg["content"])
			continue
		}
		if role != "assistant" {
			role = "user"
		}
		content := convertOpenAIContentBlocks(msg["content"])
		anthropicMsgs = append(anthropicMsgs, map[string]any{"role": role, "content": content})
	}
	out := map[string]any{
		"model":      model,
		"max_tokens": 4096,
		"messages":   anthropicMsgs,
	}
	if system != "" {
		out["system"] = system
	}
	if maxTok, ok := payload["max_tokens"].(float64); ok {
		out["max_tokens"] = int(maxTok)
	}
	return out, nil
}

func extractOpenAIContent(content any) string {
	switch v := content.(type) {
	case string:
		return v
	case []any:
		var parts []string
		for _, p := range v {
			part, ok := p.(map[string]any)
			if !ok {
				continue
			}
			if text, ok := part["text"].(string); ok {
				parts = append(parts, text)
			}
		}
		return strings.Join(parts, "\n")
	default:
		return ""
	}
}

func convertOpenAIContentBlocks(content any) any {
	switch v := content.(type) {
	case string:
		if v == "" {
			return ""
		}
		return v
	case []any:
		blocks := make([]map[string]any, 0, len(v))
		for _, p := range v {
			part, ok := p.(map[string]any)
			if !ok {
				continue
			}
			partType, _ := part["type"].(string)
			switch partType {
			case "text":
				if text, ok := part["text"].(string); ok {
					blocks = append(blocks, map[string]any{"type": "text", "text": text})
				}
			case "image_url":
				if imageURL, ok := part["image_url"].(map[string]any); ok {
					if url, ok := imageURL["url"].(string); ok && url != "" {
						blocks = append(blocks, map[string]any{
							"type": "image",
							"source": map[string]any{
								"type": "url",
								"url":  url,
							},
						})
					}
				}
			case "image":
				if source, ok := part["source"].(map[string]any); ok {
					blocks = append(blocks, map[string]any{"type": "image", "source": source})
				}
			default:
				if text, ok := part["text"].(string); ok {
					blocks = append(blocks, map[string]any{"type": "text", "text": text})
				}
			}
		}
		if len(blocks) == 1 {
			if text, ok := blocks[0]["text"].(string); ok && blocks[0]["type"] == "text" {
				return text
			}
		}
		if len(blocks) > 0 {
			return blocks
		}
		return ""
	default:
		return ""
	}
}

func convertAnthropicToOpenAI(body []byte, model string) ([]byte, int, int) {
	var resp struct {
		Content []struct {
			Text string `json:"text"`
		} `json:"content"`
		Usage struct {
			InputTokens  int `json:"input_tokens"`
			OutputTokens int `json:"output_tokens"`
		} `json:"usage"`
	}
	_ = json.Unmarshal(body, &resp)
	text := ""
	if len(resp.Content) > 0 {
		text = resp.Content[0].Text
	}
	openAI := map[string]any{
		"id":      "chatcmpl-luminary",
		"object":  "chat.completion",
		"model":   model,
		"choices": []any{map[string]any{"index": 0, "message": map[string]string{"role": "assistant", "content": text}, "finish_reason": "stop"}},
		"usage": map[string]int{
			"prompt_tokens":     resp.Usage.InputTokens,
			"completion_tokens": resp.Usage.OutputTokens,
			"total_tokens":      resp.Usage.InputTokens + resp.Usage.OutputTokens,
		},
	}
	b, _ := json.Marshal(openAI)
	return b, resp.Usage.InputTokens, resp.Usage.OutputTokens
}
