package storage

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"strings"
	"sync"
	"time"
)

type memObject struct {
	data        []byte
	contentType string
	createdAt   time.Time
}

// MemoryStore implements Store using in-memory storage.
// Mount ServeHTTP at /storage/ to serve stored objects.
type MemoryStore struct {
	objects map[string]*memObject
	mu      sync.RWMutex
	baseURL string // e.g., "http://localhost:8001/storage"
}

// NewMemoryStore creates an in-memory store. baseURL is the URL prefix for serving objects
// (e.g., "http://localhost:8001/storage").
func NewMemoryStore(baseURL string) *MemoryStore {
	return &MemoryStore{
		objects: make(map[string]*memObject),
		baseURL: strings.TrimRight(baseURL, "/"),
	}
}

func (s *MemoryStore) Upload(_ context.Context, path string, data []byte, contentType string) (string, error) {
	s.mu.Lock()
	defer s.mu.Unlock()
	s.objects[path] = &memObject{
		data:        append([]byte(nil), data...),
		contentType: contentType,
		createdAt:   time.Now(),
	}
	return fmt.Sprintf("%s/%s", s.baseURL, path), nil
}

func (s *MemoryStore) UploadPresigned(_ context.Context, path string, contentType string) (*PresignedUpload, error) {
	// In dev mode, presigned uploads go through the same /storage/ endpoint.
	return &PresignedUpload{
		URL:     fmt.Sprintf("%s/%s", s.baseURL, path),
		Headers: map[string]string{"Content-Type": contentType},
		Method:  "PUT",
		Expires: time.Now().Add(15 * time.Minute),
	}, nil
}

func (s *MemoryStore) GetURL(_ context.Context, path string) (string, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	if _, ok := s.objects[path]; !ok {
		return "", fmt.Errorf("storage: object not found: %s", path)
	}
	return fmt.Sprintf("%s/%s", s.baseURL, path), nil
}

func (s *MemoryStore) Delete(_ context.Context, path string) error {
	s.mu.Lock()
	defer s.mu.Unlock()
	delete(s.objects, path)
	return nil
}

func (s *MemoryStore) List(_ context.Context, prefix string) ([]MediaObject, error) {
	s.mu.RLock()
	defer s.mu.RUnlock()
	var result []MediaObject
	for path, obj := range s.objects {
		if strings.HasPrefix(path, prefix) {
			result = append(result, MediaObject{
				Path:        path,
				URL:         fmt.Sprintf("%s/%s", s.baseURL, path),
				ContentType: obj.contentType,
				Size:        int64(len(obj.data)),
				CreatedAt:   obj.createdAt,
			})
		}
	}
	return result, nil
}

// ServeHTTP serves stored objects and accepts PUT uploads for the dev presigned URL flow.
// This handler is dev-only — in production, clients upload directly to GCS via presigned URLs.
// PUT requests are limited to 100MB to prevent accidental OOM in development.
// Mount at /storage/ in the application router.
func (s *MemoryStore) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Strip /storage/ prefix to get the object path.
	path := strings.TrimPrefix(r.URL.Path, "/storage/")
	if path == "" {
		http.Error(w, "path required", http.StatusBadRequest)
		return
	}

	switch r.Method {
	case http.MethodGet:
		s.mu.RLock()
		obj, ok := s.objects[path]
		s.mu.RUnlock()
		if !ok {
			http.NotFound(w, r)
			return
		}
		w.Header().Set("Content-Type", obj.contentType)
		w.Header().Set("Content-Length", fmt.Sprintf("%d", len(obj.data)))
		_, _ = w.Write(obj.data)

	case http.MethodPut:
		// Limit upload size to 100MB (dev mode only — production uses GCS presigned URLs with their own limits).
		const maxUploadSize = 100 << 20
		r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
		data, err := io.ReadAll(r.Body)
		if err != nil {
			var maxBytesErr *http.MaxBytesError
			if errors.As(err, &maxBytesErr) {
				http.Error(w, "file too large (max 100MB)", http.StatusRequestEntityTooLarge)
				return
			}
			http.Error(w, "read body failed", http.StatusInternalServerError)
			return
		}
		ct := r.Header.Get("Content-Type")
		if ct == "" {
			ct = "application/octet-stream"
		}
		s.mu.Lock()
		s.objects[path] = &memObject{
			data:        data,
			contentType: ct,
			createdAt:   time.Now(),
		}
		s.mu.Unlock()
		w.WriteHeader(http.StatusOK)

	default:
		http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
	}
}
