package s3_storage

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/cache"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/constants"
	filemanager "git-molva.ru/Molva/molva-backend/services/api_gateway/internal/file_manager"
	"git-molva.ru/Molva/molva-backend/services/api_gateway/internal/object_storage"
	"github.com/aws/smithy-go/ptr"
	"mime"
	"mime/multipart"
	"path/filepath"
	"strings"
	"time"
)

type S3Storage struct {
	objectStorage object_storage.Client
	cacheClient   cache.Client
}

func NewS3Storage(objectStorage object_storage.Client, cacheClient cache.Client) *S3Storage {
	return &S3Storage{
		objectStorage: objectStorage,
		cacheClient:   cacheClient,
	}
}

func (s *S3Storage) GetFilePaths(
	ctx context.Context,
	fileType filemanager.FileType,
	parameters filemanager.ParameterTable,
) ([]string, error) {
	objectKey, category, err := s.buildObjectKey(fileType, parameters)
	if err != nil {
		return nil, err
	}

	// Сначала пытаемся получить URL из кэша
	cachedURL, err := s.getCachedURL(ctx, objectKey, category)
	if err == nil && cachedURL != "" {
		return []string{cachedURL}, nil
	}

	// Получаем линк из s3
	url, err := s.objectStorage.GetPresignedLink(ctx, objectKey, category,
		object_storage.LinkOptions{
			TTL: ptr.Duration(constants.DefaultFileTTL),
		})
	if err != nil {
		if errors.Is(err, object_storage.ErrObjectNotFound) {
			return nil, filemanager.ErrFileNotFound
		}

		return nil, fmt.Errorf("%s: %w", filemanager.ErrMsgGetPresignedURL, err)
	}

	// Кэшируем URL
	if err := s.cacheURL(ctx, objectKey, category, url); err != nil {
		return nil, fmt.Errorf("%s: %w", filemanager.ErrFailedToCacheUrl, err)
	}

	return []string{url}, nil
}

func (s *S3Storage) SaveFile(
	ctx context.Context,
	fileType filemanager.FileType,
	file multipart.File,
	fileHeader *multipart.FileHeader,
	parameters filemanager.ParameterTable,
) error {
	objectKey, category, err := s.buildObjectKey(fileType, parameters)
	if err != nil {
		return err
	}

	buf := new(bytes.Buffer)
	if _, err := buf.ReadFrom(file); err != nil {
		return fmt.Errorf("%s: %w", filemanager.ErrMsgReadFile, err)
	}

	// Определяем тип контента
	contentType := s.getContentType(fileHeader.Filename)

	// Загружаем файл в S3
	err = s.objectStorage.PutNewObject(ctx, objectKey, category,
		bytes.NewReader(buf.Bytes()),
		object_storage.PutOptions{
			ContentType: contentType,
		})
	if err != nil {
		// Обрабатываем случай существующего файла
		if errors.Is(err, object_storage.ErrObjectAlreadyExists) {
			return filemanager.ErrFileAlreadyExists
		}

		return fmt.Errorf("%s: %w", filemanager.ErrFailedUploadFile, err)
	}

	return nil
}

// Создаем ключ объекта S3 на основе типа файла и параметров
func (s *S3Storage) buildObjectKey(fileType filemanager.FileType, parameters filemanager.ParameterTable) (string, object_storage.Category, error) {
	switch fileType {
	case filemanager.AvatarFileType:
		userID, ok := parameters[filemanager.UserIdParam]
		if !ok {
			return "", "", filemanager.ErrParameterNotFound
		}

		return fmt.Sprintf(objectKeyAvatarTemplate, userID), object_storage.LogoCategory, nil

	case filemanager.CVFileType:
		submissionID, ok := parameters[filemanager.SubmissionIdParam]
		if !ok {
			return "", "", filemanager.ErrParameterNotFound
		}

		return fmt.Sprintf(objectKeyCVTemplate, submissionID), object_storage.DocumentCategory, nil

	default:
		return "", "", filemanager.ErrInvalidFileType
	}
}

// getContentType определяет тип контента по расширению
func (s *S3Storage) getContentType(filename string) string {
	ext := strings.ToLower(filepath.Ext(filename))

	if ct, ok := ContentTypes[ext]; ok {
		return ct
	}

	if ct := mime.TypeByExtension(ext); ct != "" {
		return ct
	}

	return defaultContentType
}

func (s *S3Storage) getCachedURL(ctx context.Context, objectKey string, category object_storage.Category) (string, error) {
	cacheKey := fmt.Sprintf(cacheKeyTemplate, category, objectKey)
	return s.cacheClient.Get(ctx, cacheKey, cache.DocumentsValueType)
}

func (s *S3Storage) cacheURL(ctx context.Context, objectKey string, category object_storage.Category, url string) error {
	cacheKey := fmt.Sprintf(cacheKeyTemplate, category, objectKey)
	cacheDuration := constants.DefaultFileTTL - time.Hour

	return s.cacheClient.Set(ctx, cacheKey, cache.DocumentsValueType, url, cacheDuration)
}
