package office

import (
	"encoding/json"
	"io"
	"net/http"
	"strings"

	"github.com/go-chi/chi/v5"

	"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
)

func (h *Handler) PublicShareRoutes() chi.Router {
	r := chi.NewRouter()
	h.RegisterPublicShareRoutes(r)
	return r
}

func (h *Handler) RegisterPublicShareRoutes(r chi.Router) {
	r.Post("/shares/{token}/office/session", h.PublicShareSession)
	r.Get("/shares/{token}/office/document", h.PublicShareDocument)
	r.Post("/shares/{token}/office/callback", h.PublicShareCallback)
}

type publicOfficeSessionRequest struct {
	Path     string `json:"path"`
	Mode     string `json:"mode"`
	Password string `json:"password"`
	GuestID  string `json:"guest_id"`
}

func publicSharePassword(r *http.Request) string {
	return strings.TrimSpace(r.URL.Query().Get("password"))
}

func (h *Handler) PublicShareSession(w http.ResponseWriter, r *http.Request) {
	token := strings.TrimSpace(chi.URLParam(r, "token"))
	if token == "" {
		apivalidate.WriteNotFound(w, r, "not found")
		return
	}
	var req publicOfficeSessionRequest
	if err := apivalidate.DecodeJSON(w, r, 32<<10, &req); err != nil {
		return
	}
	if strings.TrimSpace(req.Path) == "" {
		apivalidate.WriteValidationError(w, r, apivalidate.NewValidationError(
			apivalidate.FieldDetail{Field: "path", Message: "required"},
		))
		return
	}
	password := strings.TrimSpace(req.Password)
	if password == "" {
		password = publicSharePassword(r)
	}
	perms, err := h.svc.nc.EffectivePublicSharePermissions(r.Context(), token, req.Path, password)
	if err != nil {
		apivalidate.WriteInternal(w, r)
		return
	}
	if !nextcloud.PublicShareCanRead(perms) {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}
	mode := strings.TrimSpace(req.Mode)
	if mode == "" {
		mode = "edit"
	}
	if mode == "edit" && !nextcloud.PublicShareCanUpdate(perms) {
		mode = "view"
	}
	cfg, err := h.svc.PublicEditorConfig(r.Context(), token, req.Path, mode, password, req.GuestID)
	if err != nil {
		h.logger.Error("public editor config", "error", err)
		apivalidate.WriteInternal(w, r)
		return
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
		"config":    cfg,
		"serverUrl": h.svc.PublicURL(),
		"mode":      mode,
	})
}

func (h *Handler) PublicShareDocument(w http.ResponseWriter, r *http.Request) {
	token := strings.TrimSpace(chi.URLParam(r, "token"))
	filePath := strings.TrimSpace(r.URL.Query().Get("path"))
	password := publicSharePassword(r)
	sig := strings.TrimSpace(r.URL.Query().Get("sig"))
	if h.svc.Cfg.JWTSecret != "" && !VerifyPublicDocAccess(token, filePath, password, sig, h.svc.Cfg.JWTSecret) {
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}
	body, contentType, err := h.svc.OpenPublicDocument(r.Context(), PublicShareAccess{
		Token: token, FilePath: filePath, Password: password,
	})
	if err != nil {
		http.Error(w, "not found", http.StatusNotFound)
		return
	}
	defer body.Close()
	if contentType != "" {
		w.Header().Set("Content-Type", contentType)
	}
	_, _ = io.Copy(w, body)
}

func (h *Handler) PublicShareCallback(w http.ResponseWriter, r *http.Request) {
	token := strings.TrimSpace(chi.URLParam(r, "token"))
	filePath := strings.TrimSpace(r.URL.Query().Get("path"))
	password := publicSharePassword(r)
	sig := strings.TrimSpace(r.URL.Query().Get("sig"))
	if h.svc.Cfg.JWTSecret != "" && !VerifyPublicDocAccess(token, filePath, password, sig, h.svc.Cfg.JWTSecret) {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]int{"error": 1})
		return
	}

	var payload struct {
		Status int    `json:"status"`
		URL    string `json:"url"`
	}
	if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
		apiresponse.WriteJSON(w, http.StatusOK, map[string]int{"error": 1})
		return
	}
	if payload.Status == 2 || payload.Status == 6 {
		if payload.URL != "" {
			resp, err := http.Get(payload.URL)
			if err != nil {
				h.logger.Error("public office callback fetch", "error", err, "path", filePath, "status", payload.Status)
			} else if resp.StatusCode != http.StatusOK {
				h.logger.Error("public office callback fetch status", "status", resp.StatusCode, "path", filePath, "oo_status", payload.Status)
				resp.Body.Close()
			} else {
				defer resp.Body.Close()
				ct := resp.Header.Get("Content-Type")
				if err := h.svc.SavePublicDocument(r.Context(), PublicShareAccess{
					Token: token, FilePath: filePath, Password: password,
				}, resp.Body, ct); err != nil {
					h.logger.Error("public office callback save", "error", err, "path", filePath, "status", payload.Status)
				} else if payload.Status == 2 {
					h.svc.RotatePublicDocumentKeyAfterSave(r.Context(), token, filePath, password)
				}
			}
		}
	}
	apiresponse.WriteJSON(w, http.StatusOK, map[string]int{"error": 0})
}
