package service

import (
	"context"
	"errors"
	"fmt"
	"io"
	"jaecoo-be/app/module/promotions/mapper"
	"jaecoo-be/app/module/promotions/repository"
	"jaecoo-be/app/module/promotions/request"
	"jaecoo-be/app/module/promotions/response"
	"jaecoo-be/config/config"
	minioStorage "jaecoo-be/config/config"
	"jaecoo-be/utils/paginator"
	"math/rand"
	"mime"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/minio/minio-go/v7"
	"github.com/rs/zerolog"
)

type promotionsService struct {
	Repo         repository.PromotionsRepository
	Log          zerolog.Logger
	Cfg          *config.Config
	MinioStorage *minioStorage.MinioStorage
}

type PromotionsService interface {
	GetAll(req request.PromotionsQueryRequest) (promotions []*response.PromotionsResponse, paging paginator.Pagination, err error)
	GetOne(id uint) (promotion *response.PromotionsResponse, err error)
	Create(c *fiber.Ctx, req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error)
	Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error)
	Delete(id uint) (err error)
	UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
	Viewer(c *fiber.Ctx) (err error)
}

func NewPromotionsService(repo repository.PromotionsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) PromotionsService {
	return &promotionsService{
		Repo:         repo,
		Log:          log,
		Cfg:          cfg,
		MinioStorage: minioStorage,
	}
}

func (_i *promotionsService) GetAll(req request.PromotionsQueryRequest) (promotions []*response.PromotionsResponse, paging paginator.Pagination, err error) {
	promotionsEntity, paging, err := _i.Repo.GetAll(req)
	if err != nil {
		return
	}

	host := _i.Cfg.App.Domain

	for _, promotion := range promotionsEntity {
		promotions = append(promotions, mapper.PromotionsResponseMapper(promotion, host))
	}

	return
}

func (_i *promotionsService) GetOne(id uint) (promotion *response.PromotionsResponse, err error) {
	promotionEntity, err := _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	if promotionEntity == nil {
		err = errors.New("promotion not found")
		return
	}

	host := _i.Cfg.App.Domain

	promotion = mapper.PromotionsResponseMapper(promotionEntity, host)
	return
}

func (_i *promotionsService) Create(c *fiber.Ctx, req request.PromotionsCreateRequest) (promotion *response.PromotionsResponse, err error) {
	// Handle file upload if exists
	if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
		req.ThumbnailPath = filePath
	}

	promotionEntity := req.ToEntity()
	isActive := true
	promotionEntity.IsActive = &isActive

	promotionEntity, err = _i.Repo.Create(promotionEntity)
	if err != nil {
		return
	}

	host := _i.Cfg.App.Domain

	promotion = mapper.PromotionsResponseMapper(promotionEntity, host)
	return
}

func (_i *promotionsService) UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error) {
	form, err := c.MultipartForm()
	if err != nil {
		return nil, err
	}

	files := form.File[fileKey]
	if len(files) == 0 {
		return nil, nil // No file uploaded, return nil without error
	}

	fileHeader := files[0]

	// Create minio connection
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		return nil, err
	}

	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName

	// Open file
	src, err := fileHeader.Open()
	if err != nil {
		return nil, err
	}
	defer src.Close()

	// Process filename
	filename := filepath.Base(fileHeader.Filename)
	filename = strings.ReplaceAll(filename, " ", "")
	filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
	extension := filepath.Ext(fileHeader.Filename)[1:]

	// Generate unique filename
	now := time.Now()
	rand.New(rand.NewSource(now.UnixNano()))
	randUniqueId := rand.Intn(1000000)

	newFilenameWithoutExt := filenameWithoutExt + "_" + strconv.Itoa(randUniqueId)
	newFilename := newFilenameWithoutExt + "." + extension

	// Create object name with path structure
	objectName := fmt.Sprintf("promotions/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
		Interface("Uploading file", objectName).Msg("")

	// Upload file to MinIO
	_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
	if err != nil {
		_i.Log.Error().Str("timestamp", time.Now().
			Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
			Interface("Error uploading file", err).Msg("")
		return nil, err
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "Promotions:UploadFileToMinio").
		Interface("Successfully uploaded", objectName).Msg("")

	return &objectName, nil
}

func (_i *promotionsService) Update(id uint, req request.PromotionsUpdateRequest) (promotion *response.PromotionsResponse, err error) {
	promotionEntity := req.ToEntity()

	err = _i.Repo.Update(id, promotionEntity)
	if err != nil {
		return
	}

	promotionEntity, err = _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	host := _i.Cfg.App.Domain

	promotion = mapper.PromotionsResponseMapper(promotionEntity, host)
	return
}

func (_i *promotionsService) Delete(id uint) (err error) {
	err = _i.Repo.Delete(id)
	return
}

func (_i *promotionsService) Viewer(c *fiber.Ctx) (err error) {
	filename := c.Params("filename")

	// Find promotion by filename (repository will search using LIKE pattern)
	result, err := _i.Repo.FindByThumbnailPath(filename)
	if err != nil {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": true,
			"msg":   "Promotion file not found",
		})
	}

	if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": true,
			"msg":   "Promotion thumbnail path not found",
		})
	}

	ctx := context.Background()
	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
	objectName := *result.ThumbnailPath

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "Promotions:Viewer").
		Interface("data", objectName).Msg("")

	// Create minio connection
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": true,
			"msg":   err.Error(),
		})
	}

	fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
	if err != nil {
		_i.Log.Error().Str("timestamp", time.Now().
			Format(time.RFC3339)).Str("Service:Resource", "Promotions:Viewer").
			Interface("Error getting file", err).Msg("")
		return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{
			"error": true,
			"msg":   "Failed to retrieve file",
		})
	}
	defer fileContent.Close()

	// Determine Content-Type based on file extension
	contentType := mime.TypeByExtension("." + getFileExtension(objectName))
	if contentType == "" {
		contentType = "application/octet-stream" // fallback if no MIME type matches
	}

	c.Set("Content-Type", contentType)

	if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
		return err
	}

	return
}

func getFileExtension(filename string) string {
	// split file name
	parts := strings.Split(filename, ".")

	// jika tidak ada ekstensi, kembalikan string kosong
	if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
		return ""
	}

	// ambil ekstensi terakhir
	return parts[len(parts)-1]
}
