package httpapi

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

	"momswap/backend/internal/app"
	"momswap/backend/internal/store"
)

type API struct {
	service *app.Service
}

func NewAPI(svc *app.Service) *API {
	return &API{service: svc}
}

func (a *API) Routes() http.Handler {
	mux := http.NewServeMux()
	staticFiles := http.FileServer(http.Dir("web"))
	libsFiles := http.FileServer(http.Dir("libs"))

	mux.HandleFunc("GET /healthz", a.health)
	mux.HandleFunc("GET /v1/service-key", a.getServiceKey)
	mux.HandleFunc("POST /v1/auth/challenge", a.createChallenge)
	mux.HandleFunc("POST /v1/auth/login", a.login)
	mux.HandleFunc("POST /v1/auth/register", a.register)
	mux.HandleFunc("POST /v1/auth/register-by-signature", a.registerBySignature)
	mux.HandleFunc("POST /v1/invitations", a.createInvitation)
	mux.HandleFunc("GET /v1/me/keys", a.me)

	mux.HandleFunc("POST /v1/collections", a.createCollection)
	mux.HandleFunc("GET /v1/collections", a.listCollections)
	mux.HandleFunc("PATCH /v1/collections/{id}", a.updateCollection)
	mux.HandleFunc("DELETE /v1/collections/{id}", a.deleteCollection)
	mux.HandleFunc("POST /v1/collections/{id}/features", a.createFeature)
	mux.HandleFunc("GET /v1/collections/{id}/features", a.listFeatures)
	mux.HandleFunc("GET /v1/features/public", a.listPublicFeatures)
	mux.HandleFunc("PATCH /v1/features/{id}", a.patchFeature)
	mux.HandleFunc("DELETE /v1/features/{id}", a.deleteFeature)
	mux.HandleFunc("POST /v1/assets", a.createAsset)
	mux.HandleFunc("PATCH /v1/assets/{id}", a.patchAsset)
	mux.HandleFunc("POST /v1/assets/{id}/signed-upload", a.signedUpload)
	mux.HandleFunc("PUT /v1/assets/{id}/upload", a.uploadAsset)
	mux.HandleFunc("GET /v1/assets/{id}/download", a.downloadAsset)

	mux.Handle("/web/", http.StripPrefix("/web/", staticFiles))
	mux.Handle("/libs/", http.StripPrefix("/libs/", libsFiles))
	mux.Handle("/", http.RedirectHandler("/web/", http.StatusTemporaryRedirect))

	return withCORS(mux)
}

func withCORS(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Vary", "Origin, Access-Control-Request-Method, Access-Control-Request-Headers")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS, HEAD")
		w.Header().Set("Access-Control-Max-Age", "86400")
		requestHeaders := r.Header.Get("Access-Control-Request-Headers")
		if requestHeaders != "" {
			w.Header().Set("Access-Control-Allow-Headers", requestHeaders)
		} else {
			w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, Origin, X-Requested-With")
		}
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusNoContent)
			return
		}
		next.ServeHTTP(w, r)
	})
}

func writeJSON(w http.ResponseWriter, status int, payload interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}

func readJSON(r *http.Request, target interface{}) error {
	dec := json.NewDecoder(r.Body)
	dec.DisallowUnknownFields()
	return dec.Decode(target)
}

func statusFromErr(err error) int {
	switch {
	case errors.Is(err, app.ErrUnauthorized):
		return http.StatusUnauthorized
	case errors.Is(err, app.ErrForbidden):
		return http.StatusForbidden
	case errors.Is(err, app.ErrBadRequest):
		return http.StatusBadRequest
	case errors.Is(err, app.ErrAlreadyUser):
		return http.StatusConflict
	case errors.Is(err, app.ErrRegistrationNotConfigured):
		return http.StatusServiceUnavailable
	case errors.Is(err, app.ErrInviteInvalid),
		errors.Is(err, app.ErrInviteExpired),
		errors.Is(err, app.ErrInviteExhaust):
		return http.StatusBadRequest
	case errors.Is(err, app.ErrCollectionMiss), errors.Is(err, app.ErrFeatureMiss),
		errors.Is(err, app.ErrAssetMiss),
		errors.Is(err, store.ErrNotFound):
		return http.StatusNotFound
	case errors.Is(err, app.ErrStorageNotConfigured):
		return http.StatusServiceUnavailable
	default:
		return http.StatusInternalServerError
	}
}

func writeErr(w http.ResponseWriter, err error) {
	payload := map[string]string{"error": err.Error()}
	if errors.Is(err, app.ErrRegistrationNotConfigured) {
		payload["code"] = "REGISTRATION_NOT_CONFIGURED"
		payload["hint"] = "Run ./bin/gen-server-keys.sh to create etc/server-service.env, or set ADMIN_PUBLIC_KEY before starting the API."
	}
	writeJSON(w, statusFromErr(err), payload)
}

func bearerToken(r *http.Request) (string, error) {
	h := r.Header.Get("Authorization")
	if h == "" {
		return "", app.ErrUnauthorized
	}
	parts := strings.SplitN(h, " ", 2)
	if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") || parts[1] == "" {
		return "", app.ErrUnauthorized
	}
	return parts[1], nil
}

func (a *API) authUser(r *http.Request) (string, error) {
	token, err := bearerToken(r)
	if err != nil {
		return "", err
	}
	return a.service.AuthenticateSession(token)
}

func (a *API) authUserOptional(r *http.Request) (string, error) {
	if strings.TrimSpace(r.Header.Get("Authorization")) == "" {
		return "", nil
	}
	token, err := bearerToken(r)
	if err != nil {
		return "", err
	}
	return a.service.AuthenticateSession(token)
}

func (a *API) health(w http.ResponseWriter, _ *http.Request) {
	writeJSON(w, http.StatusOK, map[string]string{"status": "ok", "time": time.Now().UTC().Format(time.RFC3339)})
}

func (a *API) createChallenge(w http.ResponseWriter, r *http.Request) {
	var req struct {
		PublicKey string `json:"publicKey"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	nonce, err := a.service.CreateChallenge(req.PublicKey, clientIP(r))
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"nonce": nonce, "messageToSign": "login:" + nonce})
}

func (a *API) login(w http.ResponseWriter, r *http.Request) {
	var req struct {
		PublicKey string `json:"publicKey"`
		Nonce     string `json:"nonce"`
		Signature string `json:"signature"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	token, err := a.service.Login(req.PublicKey, req.Nonce, req.Signature, clientIP(r))
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"accessToken": token})
}

func (a *API) getServiceKey(w http.ResponseWriter, _ *http.Request) {
	pk := a.service.ServicePublicKey()
	if pk == "" {
		writeErr(w, app.ErrRegistrationNotConfigured)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"publicKey": pk})
}

func (a *API) register(w http.ResponseWriter, r *http.Request) {
	var req struct {
		PublicKey        string `json:"publicKey"`
		InvitePayloadB64 string `json:"invitePayloadB64"`
		InviteSignature  string `json:"inviteSignature"`
		ProofSignature   string `json:"proofSignature"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	if err := a.service.Register(req.PublicKey, req.InvitePayloadB64, req.InviteSignature, req.ProofSignature); err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, map[string]string{"status": "registered"})
}

func (a *API) registerBySignature(w http.ResponseWriter, r *http.Request) {
	var req struct {
		PublicKey string `json:"publicKey"`
		Signature string `json:"signature"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	if err := a.service.RegisterBySignature(req.PublicKey, req.Signature); err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, map[string]string{"status": "registered"})
}

func (a *API) createInvitation(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	var req struct {
		InvitePayloadB64 string `json:"invitePayloadB64"`
		InviteSignature  string `json:"inviteSignature"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	if err := a.service.CreateInvitation(user, req.InvitePayloadB64, req.InviteSignature); err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, map[string]string{"status": "invitation stored"})
}

func (a *API) me(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"publicKey": user})
}

func (a *API) createCollection(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	var req struct {
		Name string `json:"name"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	collection, err := a.service.CreateCollection(user, req.Name)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, collection)
}

func (a *API) listCollections(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"collections": a.service.ListCollections(user)})
}

func (a *API) updateCollection(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	collectionID := r.PathValue("id")
	var req struct {
		Name string `json:"name"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	collection, err := a.service.UpdateCollection(user, collectionID, req.Name)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, collection)
}

func (a *API) deleteCollection(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	collectionID := r.PathValue("id")
	if err := a.service.DeleteCollection(user, collectionID); err != nil {
		writeErr(w, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (a *API) createFeature(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	collectionID := r.PathValue("id")
	var req struct {
		Geometry   store.Point            `json:"geometry"`
		Properties map[string]interface{} `json:"properties"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	feature, err := a.service.CreateFeature(user, collectionID, req.Geometry, req.Properties)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusCreated, feature)
}

func (a *API) listFeatures(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	collectionID := r.PathValue("id")
	features, err := a.service.ListFeatures(user, collectionID)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"features": features})
}

func (a *API) listPublicFeatures(w http.ResponseWriter, r *http.Request) {
	kind := r.URL.Query().Get("kind")
	if kind != "" && kind != "3d" && kind != "image" {
		writeErr(w, app.ErrBadRequest)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"features": a.service.ListPublicFeatures(kind)})
}

func (a *API) deleteFeature(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	featureID := r.PathValue("id")
	if err := a.service.DeleteFeature(user, featureID); err != nil {
		writeErr(w, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (a *API) patchFeature(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	featureID := r.PathValue("id")
	var req struct {
		Geometry store.Point `json:"geometry"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	feature, err := a.service.UpdateFeatureGeometry(user, featureID, req.Geometry)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, feature)
}

func (a *API) createAsset(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	var req struct {
		FeatureID   string `json:"featureId"`
		Checksum    string `json:"checksum"`
		Ext         string `json:"ext"`
		Kind        string `json:"kind"`
		MimeType    string `json:"mimeType"`
		SizeBytes   int64  `json:"sizeBytes"`
		Name        string `json:"name"`
		Description string `json:"description"`
		IsPublic    *bool  `json:"isPublic"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	asset, created, err := a.service.CreateOrLinkAsset(user, app.CreateAssetInput{
		FeatureID:   req.FeatureID,
		Checksum:    req.Checksum,
		Ext:         req.Ext,
		Kind:        req.Kind,
		MimeType:    req.MimeType,
		SizeBytes:   req.SizeBytes,
		Name:        req.Name,
		Description: req.Description,
		Visibility:  req.IsPublic,
	})
	if err != nil {
		writeErr(w, err)
		return
	}
	status := http.StatusOK
	if created {
		status = http.StatusCreated
	}
	writeJSON(w, status, map[string]interface{}{
		"asset": asset,
		"link":  "/v1/assets/" + asset.ID + "/download",
	})
}

func (a *API) patchAsset(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	assetID := r.PathValue("id")
	var req struct {
		IsPublic bool `json:"isPublic"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	asset, err := a.service.SetAssetPublic(user, assetID, req.IsPublic)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]interface{}{"asset": asset, "link": "/v1/assets/" + asset.ID + "/download"})
}

func (a *API) signedUpload(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	assetID := r.PathValue("id")
	var req struct {
		ContentType string `json:"contentType"`
	}
	if err := readJSON(r, &req); err != nil {
		writeErr(w, app.ErrBadRequest)
		return
	}
	url, err := a.service.SignedUploadURL(user, assetID, req.ContentType)
	if err != nil {
		writeErr(w, err)
		return
	}
	writeJSON(w, http.StatusOK, map[string]string{"url": url, "method": http.MethodPut})
}

func (a *API) uploadAsset(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUser(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	assetID := r.PathValue("id")
	if err := a.service.UploadAsset(user, assetID, r.Header.Get("Content-Type"), r.Body, r.ContentLength); err != nil {
		writeErr(w, err)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

func (a *API) downloadAsset(w http.ResponseWriter, r *http.Request) {
	user, err := a.authUserOptional(r)
	if err != nil {
		writeErr(w, err)
		return
	}
	assetID := r.PathValue("id")
	reader, contentType, size, err := a.service.OpenAssetDownload(user, assetID)
	if err != nil {
		writeErr(w, err)
		return
	}
	defer reader.Close()
	if contentType != "" {
		w.Header().Set("Content-Type", contentType)
	}
	if size >= 0 {
		w.Header().Set("Content-Length", fmt.Sprintf("%d", size))
	}
	if _, err := io.Copy(w, reader); err != nil {
		// Response stream may be interrupted by client disconnects; ignore write errors here.
		return
	}
}
