package api

import (
	"context"
	_ "embed"
	"encoding/json"
	"fmt"
	"io"
	"log/slog"
	"net/http"
	"sync"
	"time"

	"github.com/jwetzell/showbridge-go/config"
	"github.com/jwetzell/showbridge-go/internal/common"
	"github.com/jwetzell/showbridge-go/internal/schema"
)

type ApiServer struct {
	config             config.ApiConfig
	serverMu           sync.Mutex
	server             *http.Server
	logger             *slog.Logger
	configurableRouter config.Configurable
	eventRouter        common.EventRouter
}

func NewApiServer(configurableRouter config.Configurable, eventRouter common.EventRouter) *ApiServer {
	return &ApiServer{
		configurableRouter: configurableRouter,
		eventRouter:        eventRouter,
		logger:             slog.Default().With("component", "api"),
	}
}

func (as *ApiServer) Start(config config.ApiConfig) {
	as.config = config
	if !as.config.Enabled {
		as.logger.Warn("not enabled")
		return
	}
	as.logger.Debug("starting", "port", as.config.Port)
	mux := http.NewServeMux()
	mux.HandleFunc("/ws", as.handleWebsocket)
	mux.HandleFunc("/health", as.handleHealthHTTP)
	mux.HandleFunc("/api/v1/config", as.handleConfigHTTP)
	mux.HandleFunc("/schema/config.schema.json", handleConfigSchema)
	mux.HandleFunc("/schema/routes.schema.json", handleRoutesSchema)
	mux.HandleFunc("/schema/modules.schema.json", handleModulesSchema)
	mux.HandleFunc("/schema/processors.schema.json", handleProcessorsSchema)

	as.serverMu.Lock()
	defer as.serverMu.Unlock()
	as.server = &http.Server{
		Addr:              fmt.Sprintf(":%d", as.config.Port),
		ReadHeaderTimeout: 5 * time.Second,
		Handler:           mux,
	}

	go func() {
		err := as.server.ListenAndServe()
		if err != nil && err != http.ErrServerClosed {
			as.logger.Error("server error", "error", err)
		}
	}()
}

func (as *ApiServer) Stop() {
	if as.server == nil {
		return
	}
	as.serverMu.Lock()
	defer as.serverMu.Unlock()
	if as.server != nil {
		apiShutdownCtx, apiShutdownCancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer apiShutdownCancel()
		err := as.server.Shutdown(apiShutdownCtx)
		if err != nil {
			as.logger.Error("error shutting down server", "error", err)
		}
		as.server = nil
	}
	as.logger.Debug("done")
}

func (as *ApiServer) handleHealthHTTP(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusOK)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func (as *ApiServer) handleConfigHTTP(w http.ResponseWriter, req *http.Request) {

	switch req.Method {
	case http.MethodGet:
		configJSON, err := json.Marshal(as.configurableRouter.GetRunningConfig())
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Write(configJSON)
	case http.MethodPut:
		//TODO(jwetzell): again way too much marshaling
		cfgBytes, err := io.ReadAll(req.Body)
		if err != nil {
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		cfgMap := make(map[string]any)
		err = json.Unmarshal(cfgBytes, &cfgMap)
		if err != nil {
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}

		err = schema.ApplyDefaults(&cfgMap)

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		err = schema.ValidateConfig(cfgMap)

		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}

		validCfgBytes, err := json.Marshal(cfgMap)
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}

		var newConfig config.Config
		err = json.Unmarshal(validCfgBytes, &newConfig)
		if err != nil {
			http.Error(w, "Bad request", http.StatusBadRequest)
			return
		}
		moduleErrors, routeErrors, err := as.configurableRouter.UpdateConfig(newConfig, true)
		if err != nil {
			http.Error(w, err.Error(), http.StatusConflict)
			return
		}
		if len(moduleErrors) > 0 || len(routeErrors) > 0 {
			errorResponse := struct {
				ModuleErrors []config.ModuleError `json:"moduleErrors,omitempty"`
				RouteErrors  []config.RouteError  `json:"routeErrors,omitempty"`
			}{
				ModuleErrors: moduleErrors,
				RouteErrors:  routeErrors,
			}
			errorResponseJSON, err := json.Marshal(errorResponse)
			if err != nil {
				http.Error(w, "Internal server error", http.StatusInternalServerError)
				return
			}
			w.Header().Set("Access-Control-Allow-Origin", "*")
			w.Header().Set("Content-Type", "application/json")
			w.WriteHeader(http.StatusBadRequest)
			w.Write(errorResponseJSON)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusOK)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, PUT, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func handleConfigSchema(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		schemaJSON, err := json.Marshal(schema.ConfigSchema)
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Write(schemaJSON)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func handleRoutesSchema(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		schemaJSON, err := json.Marshal(schema.RoutesConfigSchema)
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Write(schemaJSON)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func handleModulesSchema(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		schemaJSON, err := json.Marshal(schema.GetModulesSchema())
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Write(schemaJSON)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}

func handleProcessorsSchema(w http.ResponseWriter, req *http.Request) {
	switch req.Method {
	case http.MethodGet:
		schemaJSON, err := json.Marshal(schema.GetProcessorsSchema())
		if err != nil {
			http.Error(w, "Internal server error", http.StatusInternalServerError)
			return
		}
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Content-Type", "application/json")
		w.Write(schemaJSON)
	case http.MethodOptions:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
		w.WriteHeader(http.StatusOK)
	default:
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.WriteHeader(http.StatusMethodNotAllowed)
	}
}
