package storage

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type FilesystemStore struct {
	root string
}

func NewFilesystemStore(root string) (*FilesystemStore, error) {
	root = strings.TrimSpace(root)
	if root == "" {
		return nil, fmt.Errorf("filesystem storage root cannot be empty")
	}

	absRoot, err := filepath.Abs(root)
	if err != nil {
		return nil, fmt.Errorf("resolve filesystem storage root: %w", err)
	}

	if err := os.MkdirAll(absRoot, 0o755); err != nil {
		return nil, fmt.Errorf("create filesystem storage root: %w", err)
	}

	return &FilesystemStore{root: absRoot}, nil
}

func (s *FilesystemStore) PutObject(_ context.Context, key string, body io.Reader, opts PutObjectOptions) (ObjectMetadata, error) {
	fullPath, err := s.resolvePath(key)
	if err != nil {
		return ObjectMetadata{}, err
	}

	if err := os.MkdirAll(filepath.Dir(fullPath), 0o755); err != nil {
		return ObjectMetadata{}, fmt.Errorf("create object parent directory: %w", err)
	}

	tmpFile, err := os.CreateTemp(filepath.Dir(fullPath), ".upload-*")
	if err != nil {
		return ObjectMetadata{}, fmt.Errorf("create temp object: %w", err)
	}

	tmpName := tmpFile.Name()
	written, copyErr := io.Copy(tmpFile, body)
	closeErr := tmpFile.Close()
	if copyErr != nil {
		_ = os.Remove(tmpName)
		return ObjectMetadata{}, fmt.Errorf("write object: %w", copyErr)
	}
	if closeErr != nil {
		_ = os.Remove(tmpName)
		return ObjectMetadata{}, fmt.Errorf("close object: %w", closeErr)
	}

	if err := os.Rename(tmpName, fullPath); err != nil {
		_ = os.Remove(tmpName)
		return ObjectMetadata{}, fmt.Errorf("commit object: %w", err)
	}

	info, err := os.Stat(fullPath)
	if err != nil {
		if removeErr := os.Remove(fullPath); removeErr != nil && !errorsIsNotExist(removeErr) {
			return ObjectMetadata{}, fmt.Errorf("stat committed object: %w (cleanup failed: %v)", err, removeErr)
		}
		return ObjectMetadata{}, fmt.Errorf("stat committed object: %w", err)
	}

	contentType := opts.ContentType
	if strings.TrimSpace(contentType) == "" {
		contentType, _ = sniffFileContentType(fullPath)
	}

	return ObjectMetadata{
		Key:          key,
		Size:         written,
		ContentType:  contentType,
		LastModified: info.ModTime(),
	}, nil
}

func (s *FilesystemStore) GetObject(_ context.Context, key string) (io.ReadCloser, ObjectMetadata, error) {
	fullPath, err := s.resolvePath(key)
	if err != nil {
		return nil, ObjectMetadata{}, err
	}

	file, err := os.Open(fullPath)
	if err != nil {
		if errorsIsNotExist(err) {
			return nil, ObjectMetadata{}, ErrObjectNotFound
		}
		return nil, ObjectMetadata{}, fmt.Errorf("open object: %w", err)
	}

	info, err := file.Stat()
	if err != nil {
		_ = file.Close()
		return nil, ObjectMetadata{}, fmt.Errorf("stat object: %w", err)
	}

	contentType, err := sniffOpenFileContentType(file)
	if err != nil {
		_ = file.Close()
		return nil, ObjectMetadata{}, err
	}

	return file, ObjectMetadata{
		Key:          key,
		Size:         info.Size(),
		ContentType:  contentType,
		LastModified: info.ModTime(),
	}, nil
}

func (s *FilesystemStore) HeadObject(_ context.Context, key string) (ObjectMetadata, error) {
	fullPath, err := s.resolvePath(key)
	if err != nil {
		return ObjectMetadata{}, err
	}

	info, err := os.Stat(fullPath)
	if err != nil {
		if errorsIsNotExist(err) {
			return ObjectMetadata{}, ErrObjectNotFound
		}
		return ObjectMetadata{}, fmt.Errorf("stat object: %w", err)
	}

	contentType, _ := sniffFileContentType(fullPath)
	return ObjectMetadata{
		Key:          key,
		Size:         info.Size(),
		ContentType:  contentType,
		LastModified: info.ModTime(),
	}, nil
}

func (s *FilesystemStore) DeleteObject(_ context.Context, key string) error {
	fullPath, err := s.resolvePath(key)
	if err != nil {
		return err
	}

	if err := os.Remove(fullPath); err != nil {
		if errorsIsNotExist(err) {
			return ErrObjectNotFound
		}
		return fmt.Errorf("delete object: %w", err)
	}
	return nil
}

func (s *FilesystemStore) GeneratePresignedGetURL(_ context.Context, _ string, _ time.Duration) (string, error) {
	return "", ErrPresignUnsupported
}

func (s *FilesystemStore) resolvePath(key string) (string, error) {
	cleanKey := strings.TrimSpace(filepath.ToSlash(key))
	cleanKey = strings.TrimPrefix(cleanKey, "/")
	cleanKey = filepath.Clean(cleanKey)
	cleanKey = filepath.ToSlash(cleanKey)

	if cleanKey == "." || cleanKey == "" {
		return "", fmt.Errorf("storage key cannot be empty")
	}
	if strings.HasPrefix(cleanKey, "../") || strings.Contains(cleanKey, "/../") || cleanKey == ".." {
		return "", fmt.Errorf("invalid storage key %q", key)
	}

	fullPath := filepath.Join(s.root, filepath.FromSlash(cleanKey))
	rel, err := filepath.Rel(s.root, fullPath)
	if err != nil {
		return "", fmt.Errorf("resolve storage key %q: %w", key, err)
	}
	if rel == ".." || strings.HasPrefix(rel, ".."+string(filepath.Separator)) {
		return "", fmt.Errorf("storage key escapes configured root")
	}

	return fullPath, nil
}

func sniffFileContentType(fullPath string) (string, error) {
	file, err := os.Open(fullPath)
	if err != nil {
		return "", fmt.Errorf("open object for sniffing: %w", err)
	}
	defer file.Close()
	return sniffOpenFileContentType(file)
}

func sniffOpenFileContentType(file *os.File) (string, error) {
	sniffBuf := make([]byte, 512)
	n, err := file.Read(sniffBuf)
	if err != nil && err != io.EOF {
		return "", fmt.Errorf("sniff object content type: %w", err)
	}
	if _, err := file.Seek(0, io.SeekStart); err != nil {
		return "", fmt.Errorf("rewind object after sniffing: %w", err)
	}
	if n == 0 {
		return "application/octet-stream", nil
	}
	return http.DetectContentType(sniffBuf[:n]), nil
}

func errorsIsNotExist(err error) bool {
	return os.IsNotExist(err)
}
