package service

import (
	"context"
	"errors"
	"fmt"
	"io"
	"jaecoo-be/app/module/product_specifications/mapper"
	"jaecoo-be/app/module/product_specifications/repository"
	"jaecoo-be/app/module/product_specifications/request"
	"jaecoo-be/app/module/product_specifications/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 productSpecificationsService struct {
	Repo         repository.ProductSpecificationsRepository
	Log          zerolog.Logger
	Cfg          *config.Config
	MinioStorage *minioStorage.MinioStorage
}

type ProductSpecificationsService interface {
	GetAll(req request.ProductSpecificationsQueryRequest) (specs []*response.ProductSpecificationsResponse, paging paginator.Pagination, err error)
	GetOne(id uint) (spec *response.ProductSpecificationsResponse, err error)
	Create(c *fiber.Ctx, req request.ProductSpecificationsCreateRequest) (spec *response.ProductSpecificationsResponse, err error)
	Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error)
	Delete(id uint) (err error)
	UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
	Viewer(c *fiber.Ctx) (err error)
}

func NewProductSpecificationsService(repo repository.ProductSpecificationsRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage) ProductSpecificationsService {
	return &productSpecificationsService{
		Repo:         repo,
		Log:          log,
		Cfg:          cfg,
		MinioStorage: minioStorage,
	}
}

func (_i *productSpecificationsService) GetAll(req request.ProductSpecificationsQueryRequest) (specs []*response.ProductSpecificationsResponse, paging paginator.Pagination, err error) {
	specsEntity, paging, err := _i.Repo.GetAll(req)
	if err != nil {
		return
	}

	host := _i.Cfg.App.Domain

	for _, spec := range specsEntity {
		specs = append(specs, mapper.ProductSpecificationsResponseMapper(spec, host))
	}

	return
}

func (_i *productSpecificationsService) GetOne(id uint) (spec *response.ProductSpecificationsResponse, err error) {
	specEntity, err := _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	if specEntity == nil {
		err = errors.New("product specification not found")
		return
	}

	host := _i.Cfg.App.Domain

	spec = mapper.ProductSpecificationsResponseMapper(specEntity, host)
	return
}

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

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

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

	host := _i.Cfg.App.Domain

	spec = mapper.ProductSpecificationsResponseMapper(specEntity, host)
	return
}

func (_i *productSpecificationsService) 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("product-specifications/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "ProductSpecifications: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", "ProductSpecifications:UploadFileToMinio").
			Interface("Error uploading file", err).Msg("")
		return nil, err
	}

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

	return &objectName, nil
}

func (_i *productSpecificationsService) Update(id uint, req request.ProductSpecificationsUpdateRequest) (spec *response.ProductSpecificationsResponse, err error) {
	specEntity := req.ToEntity()

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

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

	host := _i.Cfg.App.Domain

	spec = mapper.ProductSpecificationsResponseMapper(specEntity, host)
	return
}

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

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

	// Find product specification 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":   "Product specification file not found",
		})
	}

	if result.ThumbnailPath == nil || *result.ThumbnailPath == "" {
		return c.Status(fiber.StatusNotFound).JSON(fiber.Map{
			"error": true,
			"msg":   "Product specification 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", "ProductSpecifications: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", "ProductSpecifications: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]
}
