package server

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"time"

	"go.uber.org/zap"

	"github.com/Warky-Devs/vecna.git/pkg/adapter"
	"github.com/Warky-Devs/vecna.git/pkg/config"
	"github.com/Warky-Devs/vecna.git/pkg/embedclient"
	"github.com/Warky-Devs/vecna.git/pkg/forwardproxy"
)

// ExtraMap pairs a dimension adapter with an optional forward-target override.
type ExtraMap struct {
	Adapter       adapter.Adapter
	ForwardTarget string // named target in forward.targets; empty = model-based resolution
}

// handler holds shared dependencies for all HTTP handlers.
type handler struct {
	cfg        *config.Config
	clients    map[string]embedclient.Client
	adapter    adapter.Adapter
	extraMaps  map[string]ExtraMap
	forwarders map[string]*forwardproxy.Router
	logger     *zap.Logger
}

// resolveExtraMap returns the ExtraMap for the named extra_map entry.
func (h *handler) resolveExtraMap(name string) (ExtraMap, error) {
	em, ok := h.extraMaps[name]
	if !ok {
		return ExtraMap{}, fmt.Errorf("extra_map %q not configured", name)
	}
	return em, nil
}

// resolveClient selects the embed client for the given model name.
// Returns the client, target name, and first endpoint URL for tracing.
func (h *handler) resolveClient(model string) (embedclient.Client, string, string) {
	if c, ok := h.clients[model]; ok {
		url := firstEndpointURL(h.cfg, model)
		return c, model, url
	}
	name := h.cfg.Forward.Default
	c, ok := h.clients[name]
	if !ok {
		return &errClient{err: fmt.Errorf("no client configured for model %q and no default", model)}, name, ""
	}
	return c, name, firstEndpointURL(h.cfg, name)
}

// resolveClientOverride selects the client for targetOverride when set,
// otherwise falls back to model-based resolution.
func (h *handler) resolveClientOverride(targetOverride, model string) (embedclient.Client, string, string) {
	if targetOverride == "" {
		return h.resolveClient(model)
	}
	c, ok := h.clients[targetOverride]
	if !ok {
		return &errClient{err: fmt.Errorf("extra_map forward_target %q not configured", targetOverride)}, targetOverride, ""
	}
	return c, targetOverride, firstEndpointURL(h.cfg, targetOverride)
}

// resolveForwarder selects the raw forwarder for the given model, falling back
// to forward.default when the model doesn't name a configured target directly.
func (h *handler) resolveForwarder(model string) (*forwardproxy.Router, string, error) {
	if f, ok := h.forwarders[model]; ok {
		return f, model, nil
	}
	name := h.cfg.Forward.Default
	f, ok := h.forwarders[name]
	if !ok {
		return nil, name, fmt.Errorf("no forward target configured for model %q and no default", model)
	}
	return f, name, nil
}

// resolveForwarderOverride selects the forwarder for targetOverride when set,
// otherwise falls back to model-based resolution.
func (h *handler) resolveForwarderOverride(targetOverride, model string) (*forwardproxy.Router, string, error) {
	if targetOverride == "" {
		return h.resolveForwarder(model)
	}
	f, ok := h.forwarders[targetOverride]
	if !ok {
		return nil, targetOverride, fmt.Errorf("extra_map forward_target %q not configured", targetOverride)
	}
	return f, targetOverride, nil
}

func firstEndpointURL(cfg *config.Config, targetName string) string {
	t, ok := cfg.Forward.Targets[targetName]
	if !ok || len(t.Endpoints) == 0 {
		return ""
	}
	return t.Endpoints[0].URL
}

// writeJSON encodes v as JSON and writes it with the given status code.
func writeJSON(w http.ResponseWriter, status int, v interface{}) error {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	if err := json.NewEncoder(w).Encode(v); err != nil {
		return fmt.Errorf("writeJSON: %w", err)
	}
	return nil
}

// apiError is the OpenAI-compatible error envelope, so existing OpenAI SDKs
// (which read err.error.message) surface vecna's own errors correctly instead
// of failing to parse a flat {"error": "..."} string.
type apiError struct {
	Message string `json:"message"`
	Type    string `json:"type"`
	Code    int    `json:"code"`
}

type apiErrorEnvelope struct {
	Error apiError `json:"error"`
}

// writeError writes an OpenAI-compatible error response.
func writeError(w http.ResponseWriter, status int, message string) error {
	return writeJSON(w, status, apiErrorEnvelope{Error: apiError{
		Message: message,
		Type:    errorType(status),
		Code:    status,
	}})
}

// errorType maps an HTTP status to an OpenAI-style error type string.
func errorType(status int) string {
	switch status {
	case http.StatusUnauthorized:
		return "authentication_error"
	case http.StatusBadRequest, http.StatusNotFound:
		return "invalid_request_error"
	case http.StatusBadGateway:
		return "upstream_error"
	default:
		if status >= 500 {
			return "internal_error"
		}
		return "api_error"
	}
}

// writeTraceHeaders writes X-Vecna-* timing headers from the RequestTrace.
func writeTraceHeaders(w http.ResponseWriter, t *RequestTrace) {
	total := time.Since(t.Start)
	w.Header().Set("X-Vecna-Forward-Ms", fmt.Sprintf("%d", t.ForwardDuration.Milliseconds()))
	w.Header().Set("X-Vecna-Translate-Ms", fmt.Sprintf("%d", t.TranslateDuration.Milliseconds()))
	w.Header().Set("X-Vecna-Total-Ms", fmt.Sprintf("%d", total.Milliseconds()))
}

// errClient is a Client that always returns a fixed error (used as safe fallback).
type errClient struct {
	err error
}

func (e *errClient) Embed(_ context.Context, _ embedclient.Request) (embedclient.Response, error) {
	return embedclient.Response{}, e.err
}
