package server

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"

	backendpkg "github.com/mem9-ai/drive9/pkg/backend"
	"github.com/mem9-ai/drive9/pkg/datastore"
	"github.com/mem9-ai/drive9/pkg/pathutil"
)

type fsLayerUploadInitiateRequest struct {
	Path         string `json:"path"`
	TotalSize    int64  `json:"total_size"`
	BaseRevision int64  `json:"base_revision,omitempty"`
	Mode         uint32 `json:"mode,omitempty"`
}

type fsLayerUploadCompleteRequest struct {
	Parts          []backendpkg.CompletePart `json:"parts"`
	ChecksumSHA256 string                    `json:"checksum_sha256,omitempty"`
}

func (s *Server) handleFSLayerUploads(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer, sub string) {
	if sub == "uploads/initiate" {
		if r.Method != http.MethodPost {
			errJSON(w, http.StatusMethodNotAllowed, "method not allowed")
			return
		}
		s.handleFSLayerUploadInitiate(w, r, b, layer)
		return
	}
	parts := strings.Split(strings.TrimPrefix(sub, "uploads/"), "/")
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" || r.Method != http.MethodPost {
		errJSON(w, http.StatusNotFound, "not found")
		return
	}
	uploadID, err := url.PathUnescape(parts[0])
	if err != nil || strings.TrimSpace(uploadID) == "" {
		errJSON(w, http.StatusBadRequest, "invalid upload id")
		return
	}
	switch parts[1] {
	case "presign":
		s.handleFSLayerUploadPresign(w, r, b, layer, uploadID)
	case "presign-batch":
		s.handleFSLayerUploadPresignBatch(w, r, b, layer, uploadID)
	case "complete":
		s.handleFSLayerUploadComplete(w, r, b, layer, uploadID)
	case "abort":
		s.handleFSLayerUploadAbort(w, r, b, layer, uploadID)
	default:
		errJSON(w, http.StatusNotFound, "not found")
	}
}

func (s *Server) handleFSLayerUploadInitiate(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer) {
	defer func() { _ = r.Body.Close() }()
	var req fsLayerUploadInitiateRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "invalid request body: "+err.Error())
		return
	}
	path, err := canonicalFSLayerServerPath(req.Path, "", string(datastore.FSLayerEntryKindFile))
	if err != nil {
		errJSON(w, http.StatusBadRequest, fmt.Sprintf("invalid fs layer upload path: %v", err))
		return
	}
	if !fsLayerPathWithinBase(path, layer.BaseRootPath) {
		errJSON(w, http.StatusBadRequest, "fs layer upload path is outside base root")
		return
	}
	if !authorizeFS(w, r, FSOpWrite, path) {
		return
	}
	if req.TotalSize <= 0 {
		errJSON(w, http.StatusBadRequest, "total_size must be positive")
		return
	}
	if req.TotalSize > s.maxUploadBytes {
		errJSON(w, http.StatusRequestEntityTooLarge, fmt.Sprintf("upload too large: max %d bytes", s.maxUploadBytes))
		return
	}
	if req.BaseRevision < 0 {
		errJSON(w, http.StatusBadRequest, "base_revision must be non-negative")
		return
	}
	plan, err := b.InitiateFSLayerUploadV2(r.Context(), layer.LayerID, path, req.TotalSize, req.BaseRevision, req.Mode)
	if err != nil {
		writeFSLayerUploadError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(http.StatusAccepted)
	_ = json.NewEncoder(w).Encode(plan)
}

func authorizeFSLayerUploadSession(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layerID, uploadID string, op FSOp) (*datastore.Upload, bool) {
	upload, err := b.GetUpload(r.Context(), uploadID)
	if err != nil {
		writeFSLayerUploadError(w, r, err)
		return nil, false
	}
	if upload.TargetKind != datastore.UploadTargetFSLayer || upload.LayerID != layerID {
		errJSON(w, http.StatusNotFound, "upload not found")
		return nil, false
	}
	if !authorizeFS(w, r, op, upload.TargetPath) {
		return nil, false
	}
	return upload, true
}

func (s *Server) handleFSLayerUploadPresign(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer, uploadID string) {
	if _, ok := authorizeFSLayerUploadSession(w, r, b, layer.LayerID, uploadID, FSOpWrite); !ok {
		return
	}
	defer func() { _ = r.Body.Close() }()
	var req struct {
		PartNumber int                         `json:"part_number"`
		Checksum   *backendpkg.PresignChecksum `json:"checksum,omitempty"`
	}
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "invalid request body: "+err.Error())
		return
	}
	if req.PartNumber < 1 {
		errJSON(w, http.StatusBadRequest, "part_number must be >= 1")
		return
	}
	presigned, err := b.PresignPart(r.Context(), uploadID, req.PartNumber, req.Checksum)
	if err != nil {
		writeFSLayerUploadError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(presigned)
}

func (s *Server) handleFSLayerUploadPresignBatch(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer, uploadID string) {
	if _, ok := authorizeFSLayerUploadSession(w, r, b, layer.LayerID, uploadID, FSOpWrite); !ok {
		return
	}
	defer func() { _ = r.Body.Close() }()
	var req struct {
		Parts []backendpkg.PresignPartEntry `json:"parts"`
	}
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "invalid request body: "+err.Error())
		return
	}
	if len(req.Parts) == 0 {
		errJSON(w, http.StatusBadRequest, "parts must not be empty")
		return
	}
	presigned, err := b.PresignParts(r.Context(), uploadID, req.Parts)
	if err != nil {
		writeFSLayerUploadError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]any{"parts": presigned})
}

func (s *Server) handleFSLayerUploadComplete(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer, uploadID string) {
	if _, ok := authorizeFSLayerUploadSession(w, r, b, layer.LayerID, uploadID, FSOpWrite); !ok {
		return
	}
	defer func() { _ = r.Body.Close() }()
	var req fsLayerUploadCompleteRequest
	if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, maxFSLayerBodyBytes)).Decode(&req); err != nil {
		errJSON(w, http.StatusBadRequest, "invalid request body: "+err.Error())
		return
	}
	if len(req.Parts) == 0 {
		errJSON(w, http.StatusBadRequest, "parts must not be empty")
		return
	}
	if err := validateWholeChecksumSHA256(req.ChecksumSHA256); err != nil {
		errJSON(w, http.StatusBadRequest, err.Error())
		return
	}
	if req.ChecksumSHA256 != "" && ScopeFromContext(r.Context()).IsScoped {
		errJSON(w, http.StatusForbidden, "scoped API keys cannot supply checksum_sha256")
		return
	}
	entry, published, err := b.ConfirmFSLayerUploadV2WithChecksumResult(r.Context(), uploadID, req.Parts, req.ChecksumSHA256)
	if err != nil {
		writeFSLayerUploadError(w, r, err)
		return
	}
	if published {
		recordTenantFileUsage(r.Context(), "write", "s3", entry.SizeBytes, 1)
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(toFSLayerEntryResponse(entry, false))
}

func (s *Server) handleFSLayerUploadAbort(w http.ResponseWriter, r *http.Request, b *backendpkg.Dat9Backend, layer *datastore.FSLayer, uploadID string) {
	if _, ok := authorizeFSLayerUploadSession(w, r, b, layer.LayerID, uploadID, FSOpDelete); !ok {
		return
	}
	defer func() { _ = r.Body.Close() }()
	if err := b.AbortUploadV2(r.Context(), uploadID); err != nil {
		writeFSLayerUploadError(w, r, err)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	_ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}

func writeFSLayerUploadError(w http.ResponseWriter, r *http.Request, err error) {
	switch {
	case errors.Is(err, datastore.ErrNotFound):
		errJSON(w, http.StatusNotFound, "upload not found")
	case errors.Is(err, datastore.ErrUploadExpired):
		errJSON(w, http.StatusGone, "upload expired")
	case errors.Is(err, datastore.ErrUploadNotActive),
		errors.Is(err, datastore.ErrUploadConflict),
		errors.Is(err, datastore.ErrFSLayerStateConflict),
		errors.Is(err, datastore.ErrPathConflict),
		errors.Is(err, datastore.ErrRevisionConflict):
		errJSON(w, http.StatusConflict, err.Error())
	case errors.Is(err, backendpkg.ErrUploadTooLarge):
		errJSON(w, http.StatusRequestEntityTooLarge, err.Error())
	case isBackendQuotaExceeded(err):
		errJSON(w, http.StatusInsufficientStorage, err.Error())
	case errors.Is(err, backendpkg.ErrUploadClientProtocol),
		errors.Is(err, backendpkg.ErrUnsupportedAlgorithm),
		errors.Is(err, pathutil.ErrInvalidPath):
		errJSON(w, http.StatusBadRequest, err.Error())
	default:
		writeBackendError(w, r, err)
	}
}
