package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"strings"
	"time"
)

// Config holds the application configuration
type Config struct {
	Port        int    `yaml:"port"`
	UpstreamURL string `yaml:"upstream_url"`
}

var config *Config

// blockedHeaders are headers that should never be forwarded to upstream
// for security/privacy reasons. These headers could leak internal URLs,
// session information, or other sensitive data.
var blockedHeaders = map[string]bool{
	"Referer":        true, // Don't leak internal URLs to external API
	"Cookie":         true, // Don't forward session cookies
	"Authorization":   true, // Already extracted and sent as x-api-key
	"X-Forwarded-For": true, // Don't leak client IP
	"X-Real-Ip":       true, // Don't leak client IP
	"X-Forwarded-Host": true, // Don't leak internal hostnames
}

// ClaudeCodeHeaders returns the headers to mimic claude-code CLI
func ClaudeCodeHeaders(apiKey, sessionID string) map[string]string {
	return map[string]string{
		"User-Agent":               "claude-cli/1.0.18 (pro, cli)",
		"x-api-key":                apiKey,
		"x-app":                    "cli",
		"anthropic-version":        "2023-06-01",
		"anthropic-beta":           "claude-code-20250219,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,context-management-2025-06-27,redact-thinking-2026-02-12",
		"X-Claude-Code-Session-Id": sessionID,
		"content-type":             "application/json",
	}
}

func handleModels(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodGet {
		writeError(w, http.StatusMethodNotAllowed, "Method not allowed", "invalid_request_error", "method_not_allowed")
		return
	}

	models := []map[string]interface{}{
		{"id": "claude-opus-4-5", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
		{"id": "claude-sonnet-4-20250514", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
		{"id": "claude-3-5-sonnet-20241022", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
		{"id": "claude-3-opus-20240229", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
		{"id": "claude-3-sonnet-20240229", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
		{"id": "claude-3-haiku-20240307", "object": "model", "created": 1234567890, "owned_by": "anthropic"},
	}

	response := map[string]interface{}{
		"object": "list",
		"data":   models,
	}

	w.Header().Set("content-type", "application/json")
	json.NewEncoder(w).Encode(response)
}

func handleChatCompletions(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "Method not allowed", "invalid_request_error", "method_not_allowed")
		return
	}

	// Extract Bearer token
	authHeader := r.Header.Get("Authorization")
	if authHeader == "" || !strings.HasPrefix(authHeader, "Bearer ") {
		writeError(w, http.StatusUnauthorized, "Missing or invalid Authorization header", "authentication_error", "missing_authorization")
		return
	}
	apiKey := strings.TrimPrefix(authHeader, "Bearer ")

	// Read body
	body, err := io.ReadAll(r.Body)
	if err != nil {
		writeError(w, http.StatusBadRequest, "Failed to read request body", "invalid_request_error", "body_read_error")
		return
	}

	// Decode request
	var req ChatCompletionRequest
	if err := json.Unmarshal(body, &req); err != nil {
		writeError(w, http.StatusBadRequest, "Invalid JSON in request body", "invalid_request_error", "json_decode_error")
		return
	}

	// Get session ID from context (set by main)
	sessionID := r.Context().Value(sessionIDKey).(string)

	// Convert to Anthropic format
	anthropicReq := ConvertOpenAIRequest(&req)
	anthropicReq.Stream = true // Always stream from upstream for reliability

	reqBody, _ := json.Marshal(anthropicReq)
	log.Printf("[debug] Sending to upstream %s, model=%s, body=%s", config.UpstreamURL, req.Model, string(reqBody))

	// Proxy to upstream (always streaming)
	resp, err := proxyToUpstream(anthropicReq, apiKey, sessionID, true)
	if err != nil {
		writeError(w, http.StatusBadGateway, fmt.Sprintf("Upstream request failed: %v", err), "upstream_error", "proxy_error")
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		respBody, _ := io.ReadAll(resp.Body)
		log.Printf("[debug] Upstream error status %d: %s", resp.StatusCode, string(respBody))
		writeError(w, http.StatusBadGateway, fmt.Sprintf("Upstream returned error: %s", string(respBody)), "upstream_error", fmt.Sprintf("status_%d", resp.StatusCode))
		return
	}

	isStream := req.Stream != nil && *req.Stream

	if isStream {
		// Stream to client
		w.Header().Set("content-type", "text/event-stream")
		w.Header().Set("cache-control", "no-cache")
		w.Header().Set("connection", "keep-alive")

		scanner := bufio.NewScanner(resp.Body)
		buf := make([]byte, 0, 64*1024)
		scanner.Buffer(buf, 1024*1024)

		created := int64(time.Now().Unix())
		chunkID := "chatcmpl-" + randomString(8)

		for scanner.Scan() {
			line := scanner.Text()
			log.Printf("[stream] raw: %q", line)
			if !strings.HasPrefix(line, "data: ") {
				continue
			}
			dataStr := strings.TrimPrefix(line, "data: ")
			if dataStr == "[DONE]" {
				break
			}

			var event struct {
				Type string `json:"type"`
			}
			if err := json.Unmarshal([]byte(dataStr), &event); err != nil {
				log.Printf("[stream] JSON err: %v", err)
				continue
			}
			log.Printf("[stream] type=%s", event.Type)

			openAIData := processAnthropicEvent(dataStr, chunkID, req.Model, created)
			if openAIData != "" {
				fmt.Fprintf(w, "data: %s\n\n", openAIData)
				w.(http.Flusher).Flush()
			}
		}
		fmt.Fprintf(w, "data: [DONE]\n\n")
		w.(http.Flusher).Flush()
	} else {
		// Non-streaming to client: accumulate upstream stream into a single response
		scanner := bufio.NewScanner(resp.Body)
		buf := make([]byte, 0, 64*1024)
		scanner.Buffer(buf, 1024*1024)

		var textContent string
		var stopReason string
		var usage AnthropicUsage
		msgID := ""

		for scanner.Scan() {
			line := scanner.Text()
			log.Printf("[accumulate] raw: %q", line)
			if !strings.HasPrefix(line, "data: ") {
				continue
			}
			dataStr := strings.TrimPrefix(line, "data: ")
			if dataStr == "[DONE]" {
				break
			}

			var event struct {
				Type string `json:"type"`
			}
			if err := json.Unmarshal([]byte(dataStr), &event); err != nil {
				log.Printf("[accumulate] JSON err: %v", err)
				continue
			}
			log.Printf("[accumulate] type=%s data=%.200s", event.Type, dataStr)

			switch event.Type {
			case "message_start":
				var msgStart struct {
					Message struct {
						Id    string         `json:"id"`
						Usage AnthropicUsage `json:"usage"`
					} `json:"message"`
				}
				if json.Unmarshal([]byte(dataStr), &msgStart) == nil {
					msgID = msgStart.Message.Id
					usage.InputTokens = msgStart.Message.Usage.InputTokens
				}

			case "content_block_delta":
				var delta struct {
					Delta struct {
						Text string `json:"text,omitempty"`
					} `json:"delta"`
				}
				if json.Unmarshal([]byte(dataStr), &delta) == nil {
					textContent += delta.Delta.Text
				}

			case "message_delta":
				var msgDelta struct {
					Delta      struct{}               `json:"delta"`
					Usage      *AnthropicUsage        `json:"usage,omitempty"`
					StopReason string                 `json:"stop_reason,omitempty"`
				}
				if json.Unmarshal([]byte(dataStr), &msgDelta) == nil {
					stopReason = msgDelta.StopReason
					if msgDelta.Usage != nil {
						usage.OutputTokens = msgDelta.Usage.OutputTokens
					}
				}
			}
		}

		openAIResp := &ChatCompletionResponse{
			ID:      msgID,
			Object:  "chat.completion",
			Created: time.Now().Unix(),
			Model:   req.Model,
			Choices: []Choice{
				{
					Index:        0,
					Message:      Message{Role: "assistant", Content: textContent},
					FinishReason: mapStopReason(stopReason),
				},
			},
			Usage: Usage{
				PromptTokens:     usage.InputTokens,
				CompletionTokens: usage.OutputTokens,
				TotalTokens:      usage.InputTokens + usage.OutputTokens,
			},
		}

		w.Header().Set("content-type", "application/json")
		json.NewEncoder(w).Encode(openAIResp)
	}
}

func processAnthropicEvent(dataStr, chunkID, model string, created int64) string {
	var event struct {
		Type string `json:"type"`
	}
	if err := json.Unmarshal([]byte(dataStr), &event); err != nil {
		return ""
	}

	switch event.Type {
	case "content_block_delta":
		var delta struct {
			Index int `json:"index"`
			Delta struct {
				Text           string `json:"text,omitempty"`
				InputJSONDelta string `json:"input_json_delta,omitempty"`
			} `json:"delta"`
		}
		json.Unmarshal([]byte(dataStr), &delta)

		if delta.Delta.Text != "" {
			chunk := StreamChunk{
				ID:      chunkID,
				Object:  "chat.completion.chunk",
				Created: created,
				Model:   model,
				Choices: []StreamChoice{
					{
						Index: delta.Index,
						Delta: Delta{
							Content: delta.Delta.Text,
						},
					},
				},
			}
			data, _ := json.Marshal(chunk)
			return string(data)
		}
		if delta.Delta.InputJSONDelta != "" {
			chunk := StreamChunk{
				ID:      chunkID,
				Object:  "chat.completion.chunk",
				Created: created,
				Model:   model,
				Choices: []StreamChoice{
					{
						Index: delta.Index,
						Delta: Delta{
							ToolCalls: []StreamToolCall{
								{
									Index: delta.Index,
									Function: StreamFunction{
										Arguments: delta.Delta.InputJSONDelta,
									},
								},
							},
						},
					},
				},
			}
			data, _ := json.Marshal(chunk)
			return string(data)
		}

	case "message_delta":
		var msgDelta struct {
			StopReason string `json:"stop_reason,omitempty"`
		}
		json.Unmarshal([]byte(dataStr), &msgDelta)
		chunk := StreamChunk{
			ID:      chunkID,
			Object:  "chat.completion.chunk",
			Created: created,
			Model:   model,
			Choices: []StreamChoice{
				{
					Index:        0,
					Delta:        Delta{},
					FinishReason: mapStopReason(msgDelta.StopReason),
				},
			},
		}
		data, _ := json.Marshal(chunk)
		return string(data)
	}

	return ""
}

func proxyToUpstream(req *AnthropicRequest, apiKey, sessionID string, stream bool) (*http.Response, error) {
	bodyBytes, err := json.Marshal(req)
	if err != nil {
		return nil, fmt.Errorf("failed to marshal request: %w", err)
	}

	upstreamURL := config.UpstreamURL + "/v1/messages"
	httpReq, err := http.NewRequest(http.MethodPost, upstreamURL, bytes.NewReader(bodyBytes))
	if err != nil {
		return nil, fmt.Errorf("failed to create request: %w", err)
	}

	headers := ClaudeCodeHeaders(apiKey, sessionID)
	for k, v := range headers {
		httpReq.Header.Set(k, v)
	}

	if stream {
		httpReq.Header.Set("anthropic-sse-beta", "output-2025-05-14")
	}

	ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second)
	defer cancel()

	httpReq = httpReq.WithContext(ctx)

	client := &http.Client{}
	return client.Do(httpReq)
}

func writeError(w http.ResponseWriter, code int, message, errType, errCode string) {
	w.Header().Set("content-type", "application/json")
	w.WriteHeader(code)
	resp := map[string]interface{}{
		"error": map[string]string{
			"message": message,
			"type":    errType,
			"code":    errCode,
		},
	}
	json.NewEncoder(w).Encode(resp)
}

func randomString(n int) string {
	const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
	b := make([]byte, n)
	r := rand.New(rand.NewSource(time.Now().UnixNano()))
	for i := range b {
		b[i] = letters[r.Intn(len(letters))]
	}
	return string(b)
}
