package file_storage

import (
	"context"
	"errors"
	"mime"
	"net/http"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/smithy-go"
	"github.com/kzzan/s3kit"
)

type storage struct {
	client *s3kit.Client
	bucket string
}

func NewRustFSStorage(
	endpoint string,
	accessKeyID string,
	secretAccessKey string,
	bucket string,
) (IFileStorage, error) {
	client, err := s3kit.New(s3kit.Config{
		Endpoint:        endpoint,
		AccessKeyID:     accessKeyID,
		SecretAccessKey: secretAccessKey,
		Region:          "ru-1",
	})
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
	defer cancel()
	err = client.CreateBucket(
		ctx,
		bucket,
	)
	if err != nil {
		return nil, err
	}

	return &storage{
		client: client,
		bucket: bucket,
	}, nil
}

func (s *storage) Put(ctx context.Context, file *File) error {
	return s.client.PutObjectBytes(ctx, s.bucket, file.Name, file.Data, mimeFor(file))
}

func (s *storage) Get(ctx context.Context, filename string) (*File, error) {
	data, err := s.client.GetObjectBytes(ctx, s.bucket, filename)
	if err != nil {
		// s3kit отдаёт сырые smithy-ошибки: маппим отсутствие объекта
		// (NoSuchKey/NotFound) в sentinel для прикладного слоя.
		var apiErr smithy.APIError
		if errors.As(err, &apiErr) {
			switch apiErr.ErrorCode() {
			case "NoSuchKey", "NotFound":
				return nil, ErrFileNotFound
			}
		}
		return nil, err
	}
	return &File{
		Name: filename,
		Data: data,
		Mime: mimeFromData(data, filename),
	}, nil
}

func (s *storage) Delete(ctx context.Context, filename string) error {
	return s.client.DeleteObject(ctx, s.bucket, filename)
}

func (s *storage) MimeType(filename string) string {
	if mimeType := mime.TypeByExtension(filepath.Ext(filename)); mimeType != "" {
		return mimeType
	}
	return "application/octet-stream"
}

// mimeFor — Content-Type для Put: явный Mime файла приоритетнее вывода из
// расширения, чтобы объект в S3 соответствовал фактическому содержимому.
func mimeFor(f *File) string {
	if f.Mime != "" {
		return f.Mime
	}
	return mimeFromData(f.Data, f.Name)
}

// mimeFromData — Content-Type по содержимому (первые 512 байт) с fallback
// на расширение. text/* (например, HTML) трактуется как octet-stream и
// уходит в fallback: при публичном скачивании не должно отдаваться как
// html с доверенного домена.
func mimeFromData(data []byte, filename string) string {
	detected := http.DetectContentType(data)
	if strings.HasPrefix(detected, "text/") || detected == "application/octet-stream" {
		if mimeType := mime.TypeByExtension(filepath.Ext(filename)); mimeType != "" {
			return mimeType
		}
		return "application/octet-stream"
	}
	return detected
}

// extension — расширение имени файла в нижнем регистре, без query/fragment
// (например, "photo.PNG?v=2" → ".png").
func extension(filename string) string {
	if i := strings.IndexAny(filename, "?#"); i >= 0 {
		filename = filename[:i]
	}
	return filepath.Ext(strings.ToLower(filename))
}
