package service

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

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

type chatScheduleFileService struct {
	chatScheduleFileRepository repository.ChatScheduleFileRepository
	chatScheduleRepository     repository.ChatScheduleRepository
	chatScheduleFileMapper     *mapper.ChatScheduleFileMapper
	Log                        zerolog.Logger
	Cfg                        *config.Config
	MinioStorage               *minioStorage.MinioStorage
	UsersRepo                  usersRepository.UsersRepository
}

type ChatScheduleFileService interface {
	// File management operations
	UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error
	GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error)
	GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error)
	UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error)
	DeleteChatScheduleFile(authToken string, id uint) (err error)
	Viewer(c *fiber.Ctx) error
}

func NewChatScheduleFileService(
	chatScheduleFileRepository repository.ChatScheduleFileRepository,
	chatScheduleRepository repository.ChatScheduleRepository,
	log zerolog.Logger,
	cfg *config.Config,
	minioStorage *minioStorage.MinioStorage,
	usersRepo usersRepository.UsersRepository,
) ChatScheduleFileService {
	return &chatScheduleFileService{
		chatScheduleFileRepository: chatScheduleFileRepository,
		chatScheduleRepository:     chatScheduleRepository,
		chatScheduleFileMapper:     mapper.NewChatScheduleFileMapper(),
		Log:                        log,
		Cfg:                        cfg,
		MinioStorage:               minioStorage,
		UsersRepo:                  usersRepo,
	}
}

// UploadChatScheduleFile - Upload files for chat schedule
func (_i *chatScheduleFileService) UploadChatScheduleFile(c *fiber.Ctx, chatScheduleID uint) error {
	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName

	form, err := c.MultipartForm()
	if err != nil {
		return err
	}

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

	for _, files := range form.File {
		_i.Log.Info().Str("timestamp", time.Now().
			Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
			Interface("files", files).Msg("")

		for _, fileHeader := range files {
			_i.Log.Info().Str("timestamp", time.Now().
				Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
				Interface("data", fileHeader).Msg("")

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

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

			now := time.Now()
			rand.New(rand.NewSource(now.UnixNano()))
			randUniqueId := rand.Intn(1000000)

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

			objectName := fmt.Sprintf("chat-schedules/upload/%d/%d/%s", now.Year(), now.Month(), newFilename)

			// Get file type from form data
			fileType := c.FormValue("file_type", "other")
			description := c.FormValue("description", "")
			isRequired := c.FormValue("is_required") == "true"

			// Create file entity
			fileEntity := &entity.ChatScheduleFiles{
				ChatScheduleID: chatScheduleID,
				FileName:       newFilename,
				OriginalName:   filenameAlt,
				FilePath:       objectName,
				FileSize:       fileHeader.Size,
				MimeType:       fileHeader.Header.Get("Content-Type"),
				FileType:       fileType,
				Description:    description,
				IsRequired:     isRequired,
			}

			// Save to database
			_, err = _i.chatScheduleFileRepository.CreateChatScheduleFile(fileEntity)
			if err != nil {
				return err
			}

			// Upload file to MinIO
			_, err = minioClient.PutObject(context.Background(), bucketName, objectName, src, fileHeader.Size, minio.PutObjectOptions{})
			if err != nil {
				return err
			}
		}
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::Upload").
		Interface("data", "Successfully uploaded").Msg("")

	return nil
}

// GetChatScheduleFiles - Get files for a chat schedule
func (_i *chatScheduleFileService) GetChatScheduleFiles(authToken string, req request.ChatScheduleFileQueryRequest) (files []*response.ChatScheduleFileResponse, err error) {
	userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if userInfo == nil {
		return nil, errors.New("user not found")
	}
	userID := userInfo.ID

	// If chat schedule ID is provided, check if user has access
	if req.ChatScheduleID != nil {
		isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, *req.ChatScheduleID)
		if err != nil {
			return nil, err
		}

		if !isParticipant {
			return nil, errors.New("user is not a participant in this chat session")
		}
	}

	// Get files from repository
	fileEntities, err := _i.chatScheduleFileRepository.GetChatScheduleFiles(req)
	if err != nil {
		return nil, err
	}

	// Convert to response
	files = _i.chatScheduleFileMapper.ToResponseList(fileEntities)
	return
}

// GetChatScheduleFileByID - Get a specific chat schedule file
func (_i *chatScheduleFileService) GetChatScheduleFileByID(authToken string, id uint) (file *response.ChatScheduleFileResponse, err error) {
	userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if userInfo == nil {
		return nil, errors.New("user not found")
	}
	userID := userInfo.ID

	// Get file from repository
	fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
	if err != nil {
		return nil, err
	}

	// Check if user has access to the chat schedule
	isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, fileEntity.ChatScheduleID)
	if err != nil {
		return nil, err
	}

	if !isParticipant {
		return nil, errors.New("user is not a participant in this chat session")
	}

	// Convert to response
	file = _i.chatScheduleFileMapper.ToResponse(fileEntity)
	return
}

// UpdateChatScheduleFile - Update a chat schedule file
func (_i *chatScheduleFileService) UpdateChatScheduleFile(authToken string, id uint, req request.ChatScheduleFileUpdateRequest) (err error) {
	userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if userInfo == nil {
		return errors.New("user not found")
	}
	userID := userInfo.ID

	// Get existing file to check access
	existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
	if err != nil {
		return err
	}

	// Check if user has access to the chat schedule
	isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID)
	if err != nil {
		return err
	}

	if !isParticipant {
		return errors.New("user is not a participant in this chat session")
	}

	// Convert request to entity
	file := _i.chatScheduleFileMapper.ToUpdateEntity(req)

	// Update file
	err = _i.chatScheduleFileRepository.UpdateChatScheduleFile(id, file)
	return
}

// DeleteChatScheduleFile - Delete a chat schedule file
func (_i *chatScheduleFileService) DeleteChatScheduleFile(authToken string, id uint) (err error) {
	userInfo := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if userInfo == nil {
		return errors.New("user not found")
	}
	userID := userInfo.ID

	// Get existing file to check access
	existingFile, err := _i.chatScheduleFileRepository.GetChatScheduleFileByID(id)
	if err != nil {
		return err
	}

	// Check if user has access to the chat schedule
	isParticipant, err := _i.chatScheduleRepository.CheckUserInChatSchedule(userID, existingFile.ChatScheduleID)
	if err != nil {
		return err
	}

	if !isParticipant {
		return errors.New("user is not a participant in this chat session")
	}

	// Delete file
	err = _i.chatScheduleFileRepository.DeleteChatScheduleFile(id)
	return
}

// Viewer - View chat schedule file
func (_i *chatScheduleFileService) Viewer(c *fiber.Ctx) error {
	filename := c.Params("filename")

	// Find file by filename
	fileEntity, err := _i.chatScheduleFileRepository.GetChatScheduleFileByFilename(filename)
	if err != nil {
		return err
	}

	ctx := context.Background()
	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName
	objectName := fileEntity.FilePath

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "ChatScheduleFile::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 {
		return err
	}
	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 nil
}

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

	// if no extension, return empty string
	if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
		return ""
	}

	// get last extension
	return parts[len(parts)-1]
}
