package openai

import (
	"net/http"
	"strings"
	"time"

	"iop/apps/edge/internal/authprojection"
)

func (s *Server) routes() *http.ServeMux {
	mux := http.NewServeMux()
	mux.HandleFunc("/healthz", s.handleHealthz)
	mux.HandleFunc("/v1/models", s.withAuth(s.handleModels))
	mux.HandleFunc("/v1/chat/completions", s.withAuth(s.handleChatCompletions))
	mux.HandleFunc("/v1/responses", s.withAuth(s.handleResponses))
	mux.HandleFunc("/gemini/", s.withAuth(s.handleGeminiStreamGenerateContent))
	s.registerAnthropicRoutes(mux)
	mux.HandleFunc("/api/", s.withAuth(s.handleOllamaAPI))
	return mux
}

func (s *Server) registerAnthropicRoutes(mux *http.ServeMux) {
	for _, path := range []string{"/v1/messages", "/anthropic/v1/messages"} {
		mux.HandleFunc(path, s.withAuth(s.handleAnthropicMessages))
	}
	for _, path := range []string{"/v1/messages/count_tokens", "/anthropic/v1/messages/count_tokens"} {
		mux.HandleFunc(path, s.withAuth(s.handleAnthropicCountTokens))
	}
	mux.HandleFunc("/anthropic/v1/models", s.withAuth(s.handleModels))
}

func (s *Server) withAuth(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		principal, view, ok := s.authenticatePrincipal(r)
		if !ok {
			s.writeAuthenticationFailure(w, r)
			return
		}
		ctx := withPrincipal(r.Context(), principal)
		if view.State == authprojection.StateFresh {
			ctx = withAuthenticatedProjectionView(ctx, view)
		}
		if s.managedCredentialPlane() && hasLegacyProviderCredential(r, s.cfg.ProviderAuth.FromHeader) {
			s.writeCallerProviderCredentialRejection(w, r)
			return
		}
		next(w, r.WithContext(ctx))
	}
}

func (s *Server) writeCallerProviderCredentialRejection(w http.ResponseWriter, r *http.Request) {
	const message = "caller provider credentials are not allowed in managed credential mode"
	if isAnthropicRequest(r) {
		writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", message)
		return
	}
	if isGeminiRequest(r) {
		s.writeGeminiPreIngressError(w, http.StatusBadRequest, "INVALID_ARGUMENT", message)
		return
	}
	writeError(w, http.StatusBadRequest, "invalid_request_error", message)
}

func (s *Server) writeAuthenticationFailure(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("WWW-Authenticate", `Bearer realm="iop-openai"`)
	if isAnthropicRequest(r) {
		writeAnthropicError(w, http.StatusUnauthorized, "authentication_error", "authentication failed")
		return
	}
	if isGeminiRequest(r) {
		s.writeGeminiPreIngressError(w, http.StatusUnauthorized, "UNAUTHENTICATED", "authentication failed")
		return
	}
	writeError(w, http.StatusUnauthorized, "unauthorized", "unauthorized")
}

func (s *Server) handleHealthz(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, map[string]string{"status": "ok"})
}

func (s *Server) handleModels(w http.ResponseWriter, r *http.Request) {
	anthropic := strings.HasPrefix(r.URL.Path, "/anthropic/v1/") || strings.TrimSpace(r.Header.Get(anthropicVersionHeader)) != ""
	if r.Method != http.MethodGet {
		if anthropic {
			writeAnthropicError(w, http.StatusMethodNotAllowed, "invalid_request_error", "method not allowed")
		} else {
			writeError(w, http.StatusMethodNotAllowed, "method_not_allowed", "method not allowed")
		}
		return
	}
	models, err := s.advertisedModelsForPrincipal(r.Context())
	if err != nil {
		s.writeManagedRouteError(w, r, err)
		return
	}
	if anthropic {
		if err := validateAnthropicHeaders(r); err != nil {
			writeAnthropicError(w, http.StatusBadRequest, "invalid_request_error", err.Error())
			return
		}
		writeAnthropicModels(w, models)
		return
	}
	data := make([]openAIModel, 0, len(models))
	for _, model := range models {
		data = append(data, openAIModel{
			ID:      model.ID,
			Object:  "model",
			Created: time.Now().Unix(),
			OwnedBy: "iop",
		})
	}
	writeJSON(w, http.StatusOK, openAIModelsResponse{Object: "list", Data: data})
}

type advertisedModel struct {
	ID          string
	DisplayName string
}

func (s *Server) advertisedModels() []advertisedModel {
	var models []advertisedModel
	modelCatalog := s.modelCatalogSnapshot()
	if len(modelCatalog) > 0 {
		// Provider pool catalog takes priority over legacy model_routes.
		for _, entry := range modelCatalog {
			if id := strings.TrimSpace(entry.ID); id != "" {
				if entry.ExecutionPreset != "" {
					if _, ok := s.resolveRouteDispatch(id); !ok {
						continue
					}
				}
				displayName := strings.TrimSpace(entry.DisplayName)
				if displayName == "" {
					displayName = id
				}
				models = append(models, advertisedModel{ID: id, DisplayName: displayName})
			}
		}
	} else if len(s.cfg.ModelRoutes) > 0 {
		for _, route := range s.cfg.ModelRoutes {
			id := strings.TrimSpace(route.Model)
			if id != "" && route.Target != "" {
				models = append(models, advertisedModel{ID: id, DisplayName: id})
			}
		}
	} else {
		modelIDs := s.cfg.Models
		if len(modelIDs) == 0 && s.cfg.Target != "" {
			modelIDs = []string{s.cfg.Target}
		}
		for _, id := range modelIDs {
			id = strings.TrimSpace(id)
			if id != "" {
				models = append(models, advertisedModel{ID: id, DisplayName: id})
			}
		}
	}
	return models
}
