package chat

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

// openaiCompat implements the OpenAI Chat Completions wire format,
// which OpenRouter / OpenCode / Kimi all speak. Differences between
// those three providers are limited to:
//   - base URL (e.g. /v1/chat/completions vs /zen/v1/chat/completions)
//   - prefix stripped from req.Model
//   - bearer token source (different env vars)
//
// Each concrete provider wraps this with its own constructor pinning
// those values.
type openaiCompat struct {
	name        string
	baseURL     string
	apiKey      string
	prefix      string
	httpClient  *http.Client
}

func newOpenAICompat(name, baseURL, apiKey, prefix string, timeout time.Duration) *openaiCompat {
	if timeout == 0 {
		timeout = 180 * time.Second
	}
	return &openaiCompat{
		name:       name,
		baseURL:    strings.TrimRight(baseURL, "/"),
		apiKey:     apiKey,
		prefix:     prefix,
		httpClient: &http.Client{Timeout: timeout},
	}
}

func (c *openaiCompat) Name() string    { return c.name }
func (c *openaiCompat) Available() bool { return c.apiKey != "" }

func (c *openaiCompat) Chat(ctx context.Context, req Request) (*Response, error) {
	model := StripPrefix(req.Model, c.prefix)

	body := map[string]any{
		"model":    model,
		"messages": req.Messages,
		"stream":   false,
	}
	// Anthropic 4.7 (via OpenCode → Anthropic) rejects the temperature
	// field entirely with "temperature is deprecated for this model".
	// Send only when caller explicitly set it. Nil = use upstream
	// default. Keep this true for ALL openai-compat providers — being
	// permissive about field omission is better than maintaining a
	// per-model deprecation table.
	if req.Temperature != nil {
		body["temperature"] = *req.Temperature
	}
	if req.MaxTokens > 0 {
		body["max_tokens"] = req.MaxTokens
	}
	if req.Format == "json" {
		// OpenAI-compat response_format. OpenRouter passes through;
		// OpenCode + Kimi accept it. Worst case the upstream ignores it.
		body["response_format"] = map[string]any{"type": "json_object"}
	}

	bs, _ := json.Marshal(body)
	url := c.baseURL + "/chat/completions"
	httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bs))
	if err != nil {
		return nil, err
	}
	httpReq.Header.Set("Content-Type", "application/json")
	httpReq.Header.Set("Authorization", "Bearer "+c.apiKey)
	// HTTP-Referer + X-Title are OpenRouter-specific but harmless on
	// other providers — they pass them through to upstream attribution.
	httpReq.Header.Set("HTTP-Referer", "https://golanglakehouse.local")
	httpReq.Header.Set("X-Title", "Lakehouse-Go")

	resp, err := c.httpClient.Do(httpReq)
	if err != nil {
		if errors.Is(ctx.Err(), context.DeadlineExceeded) {
			return nil, fmt.Errorf("%w: %s", ErrTimeout, c.name)
		}
		return nil, fmt.Errorf("%s: %w", c.name, err)
	}
	defer resp.Body.Close()

	rb, _ := io.ReadAll(resp.Body)
	if resp.StatusCode/100 != 2 {
		return nil, fmt.Errorf("%w: %s %d: %s", ErrUpstream, c.name, resp.StatusCode, abbrev(string(rb), 300))
	}

	var oaResp struct {
		Model   string `json:"model"`
		Choices []struct {
			Message struct {
				Content string `json:"content"`
			} `json:"message"`
			FinishReason string `json:"finish_reason"`
		} `json:"choices"`
		Usage struct {
			PromptTokens     int `json:"prompt_tokens"`
			CompletionTokens int `json:"completion_tokens"`
		} `json:"usage"`
	}
	if err := json.Unmarshal(rb, &oaResp); err != nil {
		return nil, fmt.Errorf("%s decode: %w (body=%s)", c.name, err, abbrev(string(rb), 200))
	}
	if len(oaResp.Choices) == 0 {
		return nil, fmt.Errorf("%w: %s returned 0 choices: %s", ErrUpstream, c.name, abbrev(string(rb), 200))
	}

	return &Response{
		Model:        model,
		Content:      oaResp.Choices[0].Message.Content,
		InputTokens:  oaResp.Usage.PromptTokens,
		OutputTokens: oaResp.Usage.CompletionTokens,
		FinishReason: oaResp.Choices[0].FinishReason,
	}, nil
}

// NewOpenRouter returns a provider for openrouter.ai. apiKey
// resolved at construction (typically from OPENROUTER_API_KEY env or
// /etc/lakehouse/openrouter.env).
func NewOpenRouter(apiKey string, timeout time.Duration) *openaiCompat {
	return newOpenAICompat("openrouter", "https://openrouter.ai/api/v1", apiKey, "openrouter", timeout)
}

// NewOpenCode returns a provider for OpenCode's unified Zen+Go endpoint.
// One key reaches Anthropic Opus, GPT-5, Gemini 3.1, Kimi K2.6,
// DeepSeek, GLM, Qwen, plus 4 free-tier models.
func NewOpenCode(apiKey string, timeout time.Duration) *openaiCompat {
	return newOpenAICompat("opencode", "https://opencode.ai/zen/v1", apiKey, "opencode", timeout)
}

// NewKimi returns a provider for the direct Kimi For Coding endpoint.
// `api.kimi.com` is a separate account system from api.moonshot.ai —
// keys are NOT interchangeable.
func NewKimi(apiKey string, timeout time.Duration) *openaiCompat {
	return newOpenAICompat("kimi", "https://api.kimi.com/coding/v1", apiKey, "kimi", timeout)
}
