package service

import (
	"campaign-pool-be/app/database/entity"
	"campaign-pool-be/app/module/ebooks/mapper"
	"campaign-pool-be/app/module/ebooks/repository"
	"campaign-pool-be/app/module/ebooks/request"
	"campaign-pool-be/app/module/ebooks/response"
	usersRepository "campaign-pool-be/app/module/users/repository"
	config "campaign-pool-be/config/config"
	minioStorage "campaign-pool-be/config/config"
	"campaign-pool-be/utils/paginator"
	utilSvc "campaign-pool-be/utils/service"
	"context"
	"errors"
	"fmt"
	"io"
	"path/filepath"
	"strconv"
	"strings"
	"time"

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

// EbooksService
type ebooksService struct {
	Repo         repository.EbooksRepository
	WishlistRepo repository.EbookWishlistsRepository
	PurchaseRepo repository.EbookPurchasesRepository
	Log          zerolog.Logger
	Cfg          *config.Config
	UsersRepo    usersRepository.UsersRepository
	MinioStorage *minioStorage.MinioStorage
}

// EbooksService define interface of IEbooksService
type EbooksService interface {
	All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error)
	Show(id uint) (ebook *response.EbooksResponse, err error)
	ShowBySlug(slug string) (ebook *response.EbooksResponse, err error)
	Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error)
	SavePdfFile(c *fiber.Ctx, ebookId uint) (err error)
	SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error)
	Update(id uint, req request.EbooksUpdateRequest) (err error)
	Delete(id uint) error
	SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error)
	DownloadPdf(c *fiber.Ctx, ebookId uint) error
}

// NewEbooksService init EbooksService
func NewEbooksService(
	repo repository.EbooksRepository,
	wishlistRepo repository.EbookWishlistsRepository,
	purchaseRepo repository.EbookPurchasesRepository,
	log zerolog.Logger,
	cfg *config.Config,
	usersRepo usersRepository.UsersRepository,
	minioStorage *minioStorage.MinioStorage) EbooksService {

	return &ebooksService{
		Repo:         repo,
		WishlistRepo: wishlistRepo,
		PurchaseRepo: purchaseRepo,
		Log:          log,
		UsersRepo:    usersRepo,
		MinioStorage: minioStorage,
		Cfg:          cfg,
	}
}

// All implement interface of EbooksService
func (_i *ebooksService) All(req request.EbooksQueryRequest) (ebooks []*response.EbooksResponse, paging paginator.Pagination, err error) {
	ebooksData, paging, err := _i.Repo.GetAll(req)
	if err != nil {
		return nil, paging, err
	}

	ebooks = mapper.ToEbooksResponseList(ebooksData)

	return ebooks, paging, nil
}

// Show implement interface of EbooksService
func (_i *ebooksService) Show(id uint) (ebook *response.EbooksResponse, err error) {
	ebookData, err := _i.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	ebook = mapper.ToEbooksResponse(ebookData)

	return ebook, nil
}

// ShowBySlug implement interface of EbooksService
func (_i *ebooksService) ShowBySlug(slug string) (ebook *response.EbooksResponse, err error) {
	ebookData, err := _i.Repo.FindBySlug(slug)
	if err != nil {
		return nil, err
	}

	ebook = mapper.ToEbooksResponse(ebookData)

	return ebook, nil
}

// Save implement interface of EbooksService
func (_i *ebooksService) Save(req request.EbooksCreateRequest, authToken string) (ebook *entity.Ebooks, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, errors.New("user not found")
	}

	ebookEntity := req.ToEntity()
	ebookEntity.AuthorId = user.ID
	ebookEntity.CreatedById = &user.ID

	ebookData, err := _i.Repo.Create(ebookEntity)
	if err != nil {
		return nil, err
	}

	return ebookData, nil
}

// SavePdfFile implement interface of EbooksService
func (_i *ebooksService) SavePdfFile(c *fiber.Ctx, ebookId uint) (err error) {
	// Get the uploaded file
	file, err := c.FormFile("file")
	if err != nil {
		return errors.New("file is required")
	}

	// Validate file type
	contentType := file.Header.Get("Content-Type")
	if contentType != "application/pdf" {
		return errors.New("only PDF files are allowed")
	}

	// Validate file size (max 50MB)
	if file.Size > 50*1024*1024 {
		return errors.New("file size must be less than 50MB")
	}

	// Generate unique filename
	ext := filepath.Ext(file.Filename)
	filename := fmt.Sprintf("ebook_%d_%d%s", ebookId, time.Now().Unix(), ext)

	// Upload to MinIO
	fileReader, err := file.Open()
	if err != nil {
		return err
	}
	defer fileReader.Close()

	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		return err
	}

	objectName := fmt.Sprintf("ebooks/pdfs/%s", filename)
	_, err = minioClient.PutObject(
		context.Background(),
		bucketName,
		objectName,
		fileReader,
		file.Size,
		minio.PutObjectOptions{
			ContentType: contentType,
		},
	)
	if err != nil {
		return err
	}

	// Update ebook record with file info
	ebookUpdate := &entity.Ebooks{
		PdfFilePath: &objectName,
		PdfFileName: &filename,
		PdfFileSize: &file.Size,
	}

	err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate)
	if err != nil {
		return err
	}

	return nil
}

// SaveThumbnail implement interface of EbooksService
func (_i *ebooksService) SaveThumbnail(c *fiber.Ctx, ebookId uint) (err error) {
	// Get the uploaded file
	file, err := c.FormFile("file")
	if err != nil {
		return errors.New("file is required")
	}

	// Validate file type
	contentType := file.Header.Get("Content-Type")
	if !strings.HasPrefix(contentType, "image/") {
		return errors.New("only image files are allowed")
	}

	// Validate file size (max 5MB)
	if file.Size > 5*1024*1024 {
		return errors.New("file size must be less than 5MB")
	}

	// Generate unique filename
	ext := filepath.Ext(file.Filename)
	filename := fmt.Sprintf("ebook_thumbnail_%d_%d%s", ebookId, time.Now().Unix(), ext)

	// Upload to MinIO
	fileReader, err := file.Open()
	if err != nil {
		return err
	}
	defer fileReader.Close()

	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		return err
	}

	objectName := fmt.Sprintf("ebooks/thumbnails/%s", filename)
	_, err = minioClient.PutObject(
		context.Background(),
		bucketName,
		objectName,
		fileReader,
		file.Size,
		minio.PutObjectOptions{
			ContentType: contentType,
		},
	)
	if err != nil {
		return err
	}

	// Update ebook record with thumbnail info
	ebookUpdate := &entity.Ebooks{
		ThumbnailPath: &objectName,
		ThumbnailName: &filename,
	}

	err = _i.Repo.UpdateSkipNull(ebookId, ebookUpdate)
	if err != nil {
		return err
	}

	return nil
}

// Update implement interface of EbooksService
func (_i *ebooksService) Update(id uint, req request.EbooksUpdateRequest) (err error) {
	ebookEntity := req.ToEntity()
	err = _i.Repo.Update(id, ebookEntity)
	if err != nil {
		return err
	}

	return nil
}

// Delete implement interface of EbooksService
func (_i *ebooksService) Delete(id uint) error {
	err := _i.Repo.Delete(id)
	if err != nil {
		return err
	}

	return nil
}

// SummaryStats implement interface of EbooksService
func (_i *ebooksService) SummaryStats(authToken string) (summaryStats *response.EbookSummaryStats, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, errors.New("user not found")
	}

	summaryStats, err = _i.Repo.SummaryStats(user.ID)
	if err != nil {
		return nil, err
	}

	return summaryStats, nil
}

// DownloadPdf implement interface of EbooksService
func (_i *ebooksService) DownloadPdf(c *fiber.Ctx, ebookId uint) error {
	// Get ebook data
	ebook, err := _i.Repo.FindOne(ebookId)
	if err != nil {
		return err
	}

	if ebook.PdfFilePath == nil {
		return errors.New("PDF file not found")
	}

	// Check if user has purchased this ebook
	authToken := c.Get("Authorization")
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found")
	}

	purchase, err := _i.PurchaseRepo.FindByBuyerAndEbook(user.ID, ebookId)
	if err != nil {
		return errors.New("you must purchase this ebook before downloading")
	}

	if purchase.PaymentStatus == nil || *purchase.PaymentStatus != "paid" {
		return errors.New("payment not completed")
	}

	// Get file from MinIO
	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		return err
	}

	object, err := minioClient.GetObject(
		context.Background(),
		bucketName,
		*ebook.PdfFilePath,
		minio.GetObjectOptions{},
	)
	if err != nil {
		return err
	}
	defer object.Close()

	// Read file content
	fileContent, err := io.ReadAll(object)
	if err != nil {
		return err
	}

	// Update download count
	err = _i.Repo.UpdateDownloadCount(ebookId)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to update download count")
	}

	err = _i.PurchaseRepo.UpdateDownloadCount(purchase.ID)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to update purchase download count")
	}

	// Set response headers
	c.Set("Content-Type", "application/pdf")
	c.Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", *ebook.PdfFileName))
	c.Set("Content-Length", strconv.Itoa(len(fileContent)))

	return c.Send(fileContent)
}
