package service

import (
	"context"
	"errors"
	"fmt"
	"io"
	approvalHistoriesService "jaecoo-be/app/module/approval_histories/service"
	"jaecoo-be/app/module/banners/mapper"
	"jaecoo-be/app/module/banners/repository"
	"jaecoo-be/app/module/banners/request"
	"jaecoo-be/app/module/banners/response"
	usersRepository "jaecoo-be/app/module/users/repository"
	"jaecoo-be/config/config"
	minioStorage "jaecoo-be/config/config"
	"jaecoo-be/utils/paginator"
	utilSvc "jaecoo-be/utils/service"
	"math/rand"
	"mime"
	"path/filepath"
	"strconv"
	"strings"
	"time"

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

type bannersService struct {
	Repo                    repository.BannersRepository
	Log                     zerolog.Logger
	Cfg                     *config.Config
	MinioStorage            *minioStorage.MinioStorage
	UsersRepo               usersRepository.UsersRepository
	ApprovalHistoriesService approvalHistoriesService.ApprovalHistoriesService
}

type BannersService interface {
	GetAll(req request.BannersQueryRequest) (banners []*response.BannersResponse, paging paginator.Pagination, err error)
	GetOne(id uint) (banner *response.BannersResponse, err error)
	Create(c *fiber.Ctx, req request.BannersCreateRequest) (banner *response.BannersResponse, err error)
	Update(c *fiber.Ctx, id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error)
	Delete(id uint) (err error)
	Approve(id uint, authToken string) (banner *response.BannersResponse, err error)
	Reject(id uint, authToken string, message *string) (banner *response.BannersResponse, err error)
	UploadFileToMinio(c *fiber.Ctx, fileKey string) (filePath *string, err error)
	Viewer(c *fiber.Ctx) (err error)
}

func NewBannersService(repo repository.BannersRepository, log zerolog.Logger, cfg *config.Config, minioStorage *minioStorage.MinioStorage, usersRepo usersRepository.UsersRepository, approvalHistoriesService approvalHistoriesService.ApprovalHistoriesService) BannersService {
	return &bannersService{
		Repo:                    repo,
		Log:                     log,
		Cfg:                     cfg,
		MinioStorage:            minioStorage,
		UsersRepo:               usersRepo,
		ApprovalHistoriesService: approvalHistoriesService,
	}
}

func (_i *bannersService) GetAll(req request.BannersQueryRequest) (banners []*response.BannersResponse, paging paginator.Pagination, err error) {
	bannersEntity, paging, err := _i.Repo.GetAll(req)
	if err != nil {
		return
	}

	host := _i.Cfg.App.Domain

	for _, banner := range bannersEntity {
		banners = append(banners, mapper.BannersResponseMapper(banner, host))
	}

	return
}

func (_i *bannersService) GetOne(id uint) (banner *response.BannersResponse, err error) {
	bannerEntity, err := _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	if bannerEntity == nil {
		err = errors.New("banner not found")
		return
	}

	host := _i.Cfg.App.Domain

	banner = mapper.BannersResponseMapper(bannerEntity, host)
	return
}

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

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

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

	host := _i.Cfg.App.Domain

	banner = mapper.BannersResponseMapper(bannerEntity, host)
	return
}

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

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

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

	return &objectName, nil
}

func (_i *bannersService) Update(c *fiber.Ctx, id uint, req request.BannersUpdateRequest) (banner *response.BannersResponse, err error) {
	// Handle file upload if exists
	if filePath, uploadErr := _i.UploadFileToMinio(c, "file"); uploadErr == nil && filePath != nil {
		req.ThumbnailPath = filePath
	}

	bannerEntity := req.ToEntity()

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

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

	host := _i.Cfg.App.Domain

	banner = mapper.BannersResponseMapper(bannerEntity, host)
	return
}

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

func (_i *bannersService) Approve(id uint, authToken string) (banner *response.BannersResponse, err error) {
	// Get user from token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		err = errors.New("unauthorized: user not found")
		return
	}

	// Check if user has admin role (roleId = 1)
	if user.UserRoleId != 1 {
		err = errors.New("unauthorized: only admin can approve")
		return
	}

	// Approve banner (update status_id to 2)
	err = _i.Repo.Approve(id)
	if err != nil {
		return
	}

	// Save approval history
	userID := user.ID
	err = _i.ApprovalHistoriesService.CreateHistory("banners", id, 2, "approve", &userID, nil)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to save approval history")
		// Don't return error, just log it
	}

	// Get updated banner data
	bannerEntity, err := _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	if bannerEntity == nil {
		err = errors.New("banner not found")
		return
	}

	host := _i.Cfg.App.Domain
	banner = mapper.BannersResponseMapper(bannerEntity, host)
	return
}

func (_i *bannersService) Reject(id uint, authToken string, message *string) (banner *response.BannersResponse, err error) {
	// Get user from token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		err = errors.New("unauthorized: user not found")
		return
	}

	// Check if user has admin role (roleId = 1)
	if user.UserRoleId != 1 {
		err = errors.New("unauthorized: only admin can reject")
		return
	}

	// Reject banner (update status_id to 3)
	err = _i.Repo.Reject(id)
	if err != nil {
		return
	}

	// Save rejection history
	userID := user.ID
	err = _i.ApprovalHistoriesService.CreateHistory("banners", id, 3, "reject", &userID, message)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to save rejection history")
		// Don't return error, just log it
	}

	// Get updated banner data
	bannerEntity, err := _i.Repo.FindOne(id)
	if err != nil {
		return
	}

	if bannerEntity == nil {
		err = errors.New("banner not found")
		return
	}

	host := _i.Cfg.App.Domain
	banner = mapper.BannersResponseMapper(bannerEntity, host)
	return
}

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

	// Find banner 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":   "Banner file not found",
		})
	}

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