package service

import (
	"errors"
	"mime/multipart"
	"path/filepath"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
	"github.com/google/uuid"
	"github.com/rs/zerolog"
	"gorm.io/gorm"

	"web-qudo-be/app/database/entity"
	"web-qudo-be/app/module/media_library/repository"
	"web-qudo-be/app/module/media_library/request"
	"web-qudo-be/app/module/media_library/response"
	appcfg "web-qudo-be/config/config"
	"web-qudo-be/utils/paginator"
	"web-qudo-be/utils/storage"
)

// RegisterInput is used from article/CMS upload hooks (same physical file, extra catalog row).
type RegisterInput struct {
	ClientID         *uuid.UUID
	UserID           int
	PublicURL        string
	ObjectKey        *string
	OriginalFilename *string
	FileCategory     string
	SizeBytes        *int64
	SourceType       string
	SourceLabel      *string
	ArticleFileID    *uint
}

type MediaLibraryService interface {
	UpsertRegister(in RegisterInput) error
	RegisterFromRequest(clientID *uuid.UUID, userID int, req *request.MediaLibraryRegisterRequest) error
	RegisterCMSAsset(publicURL, objectKey, sourceLabel string, file *multipart.FileHeader) error
	All(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*response.MediaLibraryItemResponse, *paginator.Pagination, error)
	Upload(clientID *uuid.UUID, userID int, c *fiber.Ctx) error
	Delete(clientID *uuid.UUID, id uint) error
}

type mediaLibraryService struct {
	Repo         repository.MediaLibraryRepository
	Cfg          *appcfg.Config
	MinioStorage *appcfg.MinioStorage
	Log          zerolog.Logger
}

func NewMediaLibraryService(
	repo repository.MediaLibraryRepository,
	cfg *appcfg.Config,
	minio *appcfg.MinioStorage,
	log zerolog.Logger,
) MediaLibraryService {
	return &mediaLibraryService{Repo: repo, Cfg: cfg, MinioStorage: minio, Log: log}
}

func ArticleFilePublicURL(cfg *appcfg.Config, fileName string) string {
	base := strings.TrimSuffix(cfg.App.Domain, "/")
	return base + "/article-files/viewer/" + strings.TrimPrefix(fileName, "/")
}

func CategoryFromFilename(name string) string {
	ext := strings.ToLower(filepath.Ext(name))
	switch ext {
	case ".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg":
		return "image"
	case ".mp4", ".webm", ".mov":
		return "video"
	case ".mp3", ".wav", ".ogg", ".m4a":
		return "audio"
	case ".pdf", ".doc", ".docx", ".txt", ".csv":
		return "document"
	default:
		return "other"
	}
}

func (s *mediaLibraryService) UpsertRegister(in RegisterInput) error {
	url := strings.TrimSpace(in.PublicURL)
	if url == "" {
		return nil
	}
	existing, err := s.Repo.FindByPublicURLAny(url)
	if err == nil {
		if !existing.IsActive {
			cat := strings.TrimSpace(in.FileCategory)
			if cat == "" {
				cat = "other"
			}
			return s.Repo.Update(existing.ID, map[string]interface{}{
				"is_active":         true,
				"object_key":        in.ObjectKey,
				"original_filename": in.OriginalFilename,
				"file_category":     cat,
				"size_bytes":        in.SizeBytes,
				"source_type":       in.SourceType,
				"source_label":      in.SourceLabel,
				"article_file_id":   in.ArticleFileID,
				"updated_at":        time.Now(),
			})
		}
		return nil
	}
	if !errors.Is(err, gorm.ErrRecordNotFound) {
		return err
	}
	cat := strings.TrimSpace(in.FileCategory)
	if cat == "" {
		cat = "other"
	}
	item := &entity.MediaLibraryItem{
		PublicURL:        url,
		ObjectKey:        in.ObjectKey,
		OriginalFilename: in.OriginalFilename,
		FileCategory:     cat,
		SizeBytes:        in.SizeBytes,
		SourceType:       in.SourceType,
		SourceLabel:      in.SourceLabel,
		ArticleFileID:    in.ArticleFileID,
		CreatedByID:      in.UserID,
		ClientID:         in.ClientID,
		IsActive:         true,
	}
	return s.Repo.Create(item)
}

func (s *mediaLibraryService) RegisterCMSAsset(publicURL, objectKey, sourceLabel string, file *multipart.FileHeader) error {
	if strings.TrimSpace(publicURL) == "" {
		return nil
	}
	var keyPtr *string
	if strings.TrimSpace(objectKey) != "" {
		k := objectKey
		keyPtr = &k
	}
	lbl := sourceLabel
	var namePtr *string
	var sz *int64
	cat := "other"
	if file != nil {
		b := filepath.Base(file.Filename)
		namePtr = &b
		ss := file.Size
		sz = &ss
		cat = CategoryFromFilename(b)
	}
	return s.UpsertRegister(RegisterInput{
		PublicURL:        publicURL,
		ObjectKey:        keyPtr,
		OriginalFilename: namePtr,
		FileCategory:     cat,
		SizeBytes:        sz,
		SourceType:       "cms",
		SourceLabel:      &lbl,
	})
}

func (s *mediaLibraryService) RegisterFromRequest(clientID *uuid.UUID, userID int, req *request.MediaLibraryRegisterRequest) error {
	cat := CategoryFromFilename(req.PublicURL)
	if req.OriginalFilename != nil {
		cat = CategoryFromFilename(*req.OriginalFilename)
	}
	if req.FileCategory != nil && strings.TrimSpace(*req.FileCategory) != "" {
		cat = strings.TrimSpace(*req.FileCategory)
	}
	return s.UpsertRegister(RegisterInput{
		ClientID:         clientID,
		UserID:           userID,
		PublicURL:        strings.TrimSpace(req.PublicURL),
		ObjectKey:        req.ObjectKey,
		OriginalFilename: req.OriginalFilename,
		FileCategory:     cat,
		SizeBytes:        req.SizeBytes,
		SourceType:       req.SourceType,
		SourceLabel:      req.SourceLabel,
		ArticleFileID:    req.ArticleFileID,
	})
}

func toResponse(e *entity.MediaLibraryItem) *response.MediaLibraryItemResponse {
	return &response.MediaLibraryItemResponse{
		ID:               e.ID,
		PublicURL:        e.PublicURL,
		ObjectKey:        e.ObjectKey,
		OriginalFilename: e.OriginalFilename,
		FileCategory:     e.FileCategory,
		SizeBytes:        e.SizeBytes,
		SourceType:       e.SourceType,
		SourceLabel:      e.SourceLabel,
		ArticleFileID:    e.ArticleFileID,
		CreatedByID:      e.CreatedByID,
		ClientID:         e.ClientID,
		CreatedAt:        e.CreatedAt,
		UpdatedAt:        e.UpdatedAt,
	}
}

func (s *mediaLibraryService) All(clientID *uuid.UUID, q string, sourceType *string, p *paginator.Pagination) ([]*response.MediaLibraryItemResponse, *paginator.Pagination, error) {
	rows, paging, err := s.Repo.GetAll(clientID, q, sourceType, p)
	if err != nil {
		return nil, paging, err
	}
	out := make([]*response.MediaLibraryItemResponse, 0, len(rows))
	for _, r := range rows {
		out = append(out, toResponse(r))
	}
	return out, paging, nil
}

func (s *mediaLibraryService) Upload(clientID *uuid.UUID, userID int, c *fiber.Ctx) error {
	file, err := c.FormFile("file")
	if err != nil {
		return err
	}
	key, previewURL, err := storage.UploadMediaLibraryObject(s.MinioStorage, file)
	if err != nil {
		return err
	}
	name := filepath.Base(file.Filename)
	sz := file.Size
	return s.UpsertRegister(RegisterInput{
		ClientID:         clientID,
		UserID:           userID,
		PublicURL:        previewURL,
		ObjectKey:        &key,
		OriginalFilename: &name,
		FileCategory:     CategoryFromFilename(name),
		SizeBytes:        &sz,
		SourceType:       "upload",
		SourceLabel:      strPtr("media_library_direct"),
	})
}

func strPtr(s string) *string { return &s }

func (s *mediaLibraryService) Delete(clientID *uuid.UUID, id uint) error {
	return s.Repo.SoftDelete(clientID, id)
}
