package agecemorg

import (
	"context"
	"errors"
	"fmt"
	"mime"
	"mime/multipart"
	"net/http"

	"github.com/minio/minio-go/v7"
	"github.com/minio/minio-go/v7/pkg/credentials"
	"github.com/spf13/viper"
)

func NewMediaClient(endpoint, accessKeyId, secretAccessKey string, useSSL bool) (*MediaClient, error) {
	if accessKeyId == "" {
		return nil, errors.New("accessKeyId was found empty, but cannot be")
	}

	if secretAccessKey == "" {
		return nil, errors.New("secretAccessKey was found empty, but cannot be")
	}

	var mediaClient MediaClient
	minioClient, err := minio.New(endpoint, &minio.Options{
		Creds:  credentials.NewStaticV4(accessKeyId, secretAccessKey, ""),
		Secure: useSSL,
	})
	if err != nil {
		return &mediaClient, err
	}

	mediaClient.MinioClient = *minioClient
	return &mediaClient, nil
}

func NewMediaClientFromViper() (*MediaClient, error) {
	var cfg Config
	if err := viper.Unmarshal(&cfg); err != nil {
		return nil, err
	}

	mediaClient, err := NewMediaClient(cfg.Server.Documents.Endpoint, cfg.Server.Documents.AccessKeyId, cfg.Server.Documents.SecretAccessKey, cfg.Server.Documents.UseSSL)
	if err != nil {
		return mediaClient, err
	}

	return mediaClient, nil
}

type MediaClient struct {
	MinioClient minio.Client
}

func (m *MediaClient) Seed() ([]string, error) {
	var cfg Config
	if err := viper.Unmarshal(&cfg); err != nil {
		return nil, err
	}

	var new_buckets []string

	for bucket := range cfg.Server.Documents.Buckets {
		exists, err := m.MinioClient.BucketExists(context.Background(), bucket)
		if err != nil {
			return new_buckets, err
		}

		if exists {
			continue
		}

		if err = m.MinioClient.MakeBucket(context.Background(), bucket, minio.MakeBucketOptions{}); err != nil {
			return new_buckets, err
		}
		new_buckets = append(new_buckets, bucket)
	}

	return new_buckets, nil
}

func (m *MediaClient) UploadFormFiles(bucketName string, fileHeaders []*multipart.FileHeader) (statusCode int, result string) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	ok, err := m.MinioClient.BucketExists(ctx, bucketName)
	if err != nil {
		return http.StatusInternalServerError, fmt.Sprintf("Erreur lors de vérification d'existence de bucket '%s': %s", bucketName, err)
	}
	if !ok {
		return http.StatusBadRequest, fmt.Sprintf("Bucket '%s' n'existe pas", bucketName)
	}

	switch count := len(fileHeaders); count {
	case 0:
		return http.StatusBadRequest, "Veuillez sélectionner au moins 1 document à téléverser"
	case 1:
		result = "Téléversement de 1 fichier\n"
	default:
		result = fmt.Sprintf("Téléversement de %d fichiers\n", count)
	}

	var allowedMediaTypes = []string{"application/pdf", "text/markdown", "text/plain"}

	var fileNames []string
	for _, fileHeader := range fileHeaders {
		fileNames = append(fileNames, fileHeader.Filename)
	}

	var validFileHeaders []*multipart.FileHeader

	for i, fileHeader := range fileHeaders {
		// Check for conflicting file names in upload
		for j, fileName := range fileNames {
			if fileName == fileHeader.Filename && i != j {
				return http.StatusBadRequest, fmt.Sprintf("Doublon de nom de fichier '%s' trouvé, les noms de fichiers doivent être uniques", fileName)
			}
		}

		// Check media type
		mediaType, _, err := mime.ParseMediaType(fileHeader.Header.Get("Content-Type"))
		if err != nil {
			return http.StatusBadRequest, fmt.Sprintf("Impossible de déterminer le type de fichier pour %d '%s'.\nPlus de détails: %s", i, fileHeader.Filename, err.Error())
		}

		var isAllowedMediaType bool

		for _, allowedMediaType := range allowedMediaTypes {
			if allowedMediaType == mediaType {
				isAllowedMediaType = true
			}
		}

		if !isAllowedMediaType {
			return http.StatusUnsupportedMediaType, fmt.Sprintf("Type de fichier interdit '%s' pour '%s'.\nTypes de fichiers permis: %s", mediaType, fileHeader.Filename, allowedMediaTypes)
		}

		// Check for conflicting fileNames with existing files
		objectInfo, err := m.MinioClient.StatObject(ctx, bucketName, fileHeader.Filename, minio.StatObjectOptions{})
		if err == nil && objectInfo.Key == fileHeader.Filename {
			return http.StatusConflict, fmt.Sprintf("Un document au nom '%s' de catégorie '%s' existe déjà et ne peut pas être inséré de cette façon.", fileHeader.Filename, bucketName)
		}

		switch msg := err.Error(); msg {
		case "The specified key does not exist.":
		default:
			return http.StatusInternalServerError, fmt.Sprintf("Erreur inattendue lors de vérification de conflit de nom de fichier avec la base de données: %s", err)
		}

		validFileHeaders = append(validFileHeaders, fileHeader)
	}

	if len(validFileHeaders) == 0 {
		return http.StatusOK, "Aucun fichier valide envoyé au serveur, rien à faire."
	}

	for i, fileHeader := range validFileHeaders {
		mediaType, _, err := mime.ParseMediaType(fileHeader.Header.Get("Content-Type"))
		if err != nil {
			return http.StatusBadRequest, fmt.Sprintf("Impossible de déterminer le type de fichier pour %d '%s'.\nPlus de détails: %s", i, fileHeader.Filename, err.Error())
		}

		// Get file content
		fileContent, err := fileHeader.Open()
		if err != nil {
			return http.StatusBadRequest, fmt.Sprintf("Impossible de lire le contenu de '%s': %s", fileHeader.Filename, err)
		}
		defer fileContent.Close()

		// Upload file
		info, err := m.MinioClient.PutObject(ctx, bucketName, fileHeader.Filename, fileContent, fileHeader.Size, minio.PutObjectOptions{
			ContentType: mediaType,
		})
		if err != nil {
			return http.StatusInternalServerError, fmt.Sprintf("Impossible d'ajouter '%s' à la base de donnée: %s", fileHeader.Filename, err)
		}

		result = fmt.Sprintf("%sDocument %d '%s' de type '%s' et de taille '%d' téléversé à '%s' avec succès\n",
			result, i, info.Key, mediaType, info.Size, info.Bucket)
	}

	return http.StatusCreated, result
}