package service

import (
	"context"
	"fmt"
	"io"
	"mime"
	"mime/multipart"
	"narasi-ahli-be/app/module/knowledge_base/mapper"
	"narasi-ahli-be/app/module/knowledge_base/repository"
	"narasi-ahli-be/app/module/knowledge_base/request"
	"narasi-ahli-be/app/module/knowledge_base/response"
	config "narasi-ahli-be/config/config"
	minioStorage "narasi-ahli-be/config/config"
	"narasi-ahli-be/utils/paginator"
	"path/filepath"
	"strings"
	"time"

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

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

type KnowledgeBaseServiceInterface interface {
	All(req request.KnowledgeBaseQueryRequest) (data []*response.KnowledgeBaseResponse, paging paginator.Pagination, err error)
	Show(id uint) (data *response.KnowledgeBaseResponse, err error)

	Create(c *fiber.Ctx) (data *response.KnowledgeBaseResponse, err error)

	Update(id uint, req request.KnowledgeBaseUpdateRequest) (data *response.KnowledgeBaseResponse, err error)
	Delete(id uint) error
	Viewer(c *fiber.Ctx) error 
	UpdateStatus(id uint, status int) (data *response.KnowledgeBaseResponse, err error)
	UpdateDocumentId(id uint, documentId int) (data *response.KnowledgeBaseResponse, err error)



}

func NewKnowledgeBaseService(
	repo repository.KnowledgeBaseRepositoryInterface,
	log zerolog.Logger,
	cfg *config.Config,
	minioStorage *minioStorage.MinioStorage,
) KnowledgeBaseServiceInterface {
	return &KnowledgeBaseService{
		Repo:         repo,
		Log:          log,
		Cfg:          cfg,
		MinioStorage: minioStorage,
	}
}

func (s *KnowledgeBaseService) uploadFileToMinio(
	ctx context.Context,
	minioClient *minio.Client,
	bucketName string,
	agentId string,
	folder string,
	fileHeader *multipart.FileHeader,
) (*string, error) {

	if fileHeader == nil {
		return nil, nil
	}

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

	filename := filepath.Base(fileHeader.Filename)
	filename = strings.ReplaceAll(filename, " ", "")

	ext := filepath.Ext(filename)
	filenameWithoutExt := strings.TrimSuffix(filename, ext)

	now := time.Now()
	newFilename := fmt.Sprintf("%s_%d%s", filenameWithoutExt, now.UnixNano(), ext)

	objectName := fmt.Sprintf(
		"knowledge-base/%s/%s/%d/%d/%s",
		agentId,
		folder,
		now.Year(),
		now.Month(),
		newFilename,
	)

	_, err = minioClient.PutObject(
		ctx,
		bucketName,
		objectName,
		src,
		fileHeader.Size,
		minio.PutObjectOptions{
			ContentType: mime.TypeByExtension(ext),
		},
	)
	if err != nil {
		return nil, err
	}

	// 🔥 RETURN OBJECT PATH, BUKAN FILENAME
	return &objectName, nil
}

func (s *KnowledgeBaseService) All(req request.KnowledgeBaseQueryRequest) (data []*response.KnowledgeBaseResponse, paging paginator.Pagination, err error) {

	results, paging, err := s.Repo.GetAll(req)
	if err != nil {
		return nil, paging, err
	}

	host := s.Cfg.App.Domain
	for _, item := range results {
		data = append(data, mapper.KnowledgeBaseResponseMapper(item, host))
	}

	return data, paging, nil
}

func getFileExtension(filename string) string {
	parts := strings.Split(filename, ".")

	if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
		return ""
	}

	return parts[len(parts)-1]
}

func (s *KnowledgeBaseService) Viewer(c *fiber.Ctx) error {

	filename := c.Params("filename")

	result, folder, err := s.Repo.FindByFilename(filename)
	if err != nil {
		return fiber.NewError(fiber.StatusNotFound, "file not found")
	}

	var objectName string

	switch folder {
	case "journal":
		objectName = *result.FileJournalUrl
	case "audio":
		objectName = *result.FileAudioUrl
	case "video":
		objectName = *result.FileVideoUrl
	default:
		return fiber.NewError(fiber.StatusNotFound, "invalid file type")
	}

	ctx := context.Background()
	bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName

	minioClient, err := s.MinioStorage.ConnectMinio()
	if err != nil {
		return fiber.ErrInternalServerError
	}

	fileContent, err := minioClient.GetObject(ctx, bucketName, objectName, minio.GetObjectOptions{})
	if err != nil {
		return fiber.NewError(fiber.StatusNotFound, "file not found in storage")
	}
	defer fileContent.Close()

	contentType := mime.TypeByExtension("." + getFileExtension(objectName))
	if contentType == "" {
		contentType = "application/octet-stream"
	}

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

	_, err = io.Copy(c.Response().BodyWriter(), fileContent)
	return err
}

func (s *KnowledgeBaseService) Show(id uint) (data *response.KnowledgeBaseResponse, err error) {
	result, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	host := s.Cfg.App.Domain
	return mapper.KnowledgeBaseResponseMapper(result, host), nil
}

func (s *KnowledgeBaseService) Create(c *fiber.Ctx) (data *response.KnowledgeBaseResponse, err error) {
	req := new(request.KnowledgeBaseCreateRequest)
	if err := c.BodyParser(req); err != nil {
		return nil, err
	}

	if req.AgentId == nil || *req.AgentId == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "agentId is required")
	}
	if req.AgentName == nil || *req.AgentName == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "agentName is required")
	}
	if req.Title == nil || *req.Title == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "title is required")
	}

	fileJournal, _ := c.FormFile("fileJournal")
	fileAudio, _ := c.FormFile("fileAudio")
	fileVideo, _ := c.FormFile("fileVideo")

	minioClient, err := s.MinioStorage.ConnectMinio()
	if err != nil {
		return nil, err
	}

	ctx := context.Background()
	bucketName := s.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName

	journalFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "journal", fileJournal)
	if err != nil {
		return nil, err
	}
	audioFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "audio", fileAudio)
	if err != nil {
		return nil, err
	}
	videoFilename, err := s.uploadFileToMinio(ctx, minioClient, bucketName, *req.AgentId, "video", fileVideo)
	if err != nil {
		return nil, err
	}

	entity := req.ToEntity()


	if journalFilename != nil {
		entity.FileJournalUrl = journalFilename
	}
	if audioFilename != nil {
		entity.FileAudioUrl = audioFilename
	}
	if videoFilename != nil {
		entity.FileVideoUrl = videoFilename
	}

	if req.Status < 0 {
		entity.Status = 0
	}

	err = s.Repo.Create(entity)
	if err != nil {
		return nil, err
	}

	result, err := s.Repo.FindOne(entity.ID)
	if err != nil {
		return nil, err
	}

	host := s.Cfg.App.Domain
	return mapper.KnowledgeBaseResponseMapper(result, host), nil
}


func (s *KnowledgeBaseService) Update(id uint, req request.KnowledgeBaseUpdateRequest) (data *response.KnowledgeBaseResponse, err error) {

	old, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	old.AgentId = req.AgentId
	old.AgentName = req.AgentName
	old.Title = req.Title
	old.Status = req.Status

	old.FileJournalUrl = req.FileJournalUrl
	old.FileAudioUrl = req.FileAudioUrl
	old.FileVideoUrl = req.FileVideoUrl

	if req.IsActive != nil {
		old.IsActive = *req.IsActive
	}

	old.UpdatedAt = time.Now()

	err = s.Repo.Update(id, old)
	if err != nil {
		return nil, err
	}

	updated, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	host := s.Cfg.App.Domain
	return mapper.KnowledgeBaseResponseMapper(updated, host), nil
}

func (s *KnowledgeBaseService) Delete(id uint) error {
	result, err := s.Repo.FindOne(id)
	if err != nil {
		return err
	}

	result.IsActive = false
	result.UpdatedAt = time.Now()

	return s.Repo.Delete(id)
}

func (s *KnowledgeBaseService) UpdateStatus(id uint, status int) (data *response.KnowledgeBaseResponse, err error) {
	old, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	old.Status = status
	old.UpdatedAt = time.Now()

	err = s.Repo.Update(id, old)
	if err != nil {
		return nil, err
	}

	updated, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	host := s.Cfg.App.Domain
	return mapper.KnowledgeBaseResponseMapper(updated, host), nil
}

func (s *KnowledgeBaseService) UpdateDocumentId(id uint, documentId int) (data *response.KnowledgeBaseResponse, err error) {
	// cek data exist
	_, err = s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	err = s.Repo.UpdateDocumentId(id, documentId)
	if err != nil {
		return nil, err
	}

	updated, err := s.Repo.FindOne(id)
	if err != nil {
		return nil, err
	}

	host := s.Cfg.App.Domain
	return mapper.KnowledgeBaseResponseMapper(updated, host), nil
}

