package services

import (
	"encoding/json"
	"fmt"
	"log"
	"mime/multipart"
	"os"
	"path/filepath"
	"time"

	"rijig/dto"
	"rijig/internal/repositories"
	"rijig/model"
	"rijig/utils"

	"github.com/google/uuid"
)

type ArticleService interface {
	CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
	GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error)
	GetArticleByID(id string) (*dto.ArticleResponseDTO, error)
	UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error)
	DeleteArticle(id string) error
}

type articleService struct {
	ArticleRepo repositories.ArticleRepository
}

func NewArticleService(articleRepo repositories.ArticleRepository) ArticleService {
	return &articleService{ArticleRepo: articleRepo}
}

func (s *articleService) saveCoverArticle(coverArticle *multipart.FileHeader) (string, error) {
	pathImage := "/uploads/articles/"
	coverArticleDir := "./public" + os.Getenv("BASE_URL") + pathImage
	if _, err := os.Stat(coverArticleDir); os.IsNotExist(err) {

		if err := os.MkdirAll(coverArticleDir, os.ModePerm); err != nil {
			return "", fmt.Errorf("failed to create directory for cover article: %v", err)
		}
	}

	allowedExtensions := map[string]bool{".jpg": true, ".jpeg": true, ".png": true, ".svg": true}
	extension := filepath.Ext(coverArticle.Filename)
	if !allowedExtensions[extension] {
		return "", fmt.Errorf("invalid file type, only .jpg, .jpeg, and .png are allowed")
	}

	coverArticleFileName := fmt.Sprintf("%s_coverarticle%s", uuid.New().String(), extension)
	coverArticlePath := filepath.Join(coverArticleDir, coverArticleFileName)

	src, err := coverArticle.Open()
	if err != nil {
		return "", fmt.Errorf("failed to open uploaded file: %v", err)
	}
	defer src.Close()

	dst, err := os.Create(coverArticlePath)
	if err != nil {
		return "", fmt.Errorf("failed to create cover article file: %v", err)
	}
	defer dst.Close()

	if _, err := dst.ReadFrom(src); err != nil {
		return "", fmt.Errorf("failed to save cover article: %v", err)
	}

	iconTrashUrl := fmt.Sprintf("%s%s", pathImage, coverArticleFileName)

	return iconTrashUrl, nil
}

func deleteCoverArticle(imagePath string) error {
	if imagePath == "" {
		return nil
	}

	baseDir := "./public/" + os.Getenv("BASE_URL")
	absolutePath := baseDir + imagePath

	if _, err := os.Stat(absolutePath); os.IsNotExist(err) {
		return fmt.Errorf("image file not found: %v", err)
	}

	err := os.Remove(absolutePath)
	if err != nil {
		return fmt.Errorf("failed to delete image: %v", err)
	}

	log.Printf("Image deleted successfully: %s", absolutePath)
	return nil
}

func (s *articleService) CreateArticle(request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {

	coverArticlePath, err := s.saveCoverArticle(coverImage)
	if err != nil {
		return nil, fmt.Errorf("gagal menyimpan ikon sampah: %v", err)
	}

	article := model.Article{
		Title:      request.Title,
		CoverImage: coverArticlePath,
		Author:     request.Author,
		Heading:    request.Heading,
		Content:    request.Content,
	}

	if err := s.ArticleRepo.CreateArticle(&article); err != nil {
		return nil, fmt.Errorf("failed to create article: %v", err)
	}

	createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
	updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)

	articleResponseDTO := &dto.ArticleResponseDTO{
		ID:          article.ID,
		Title:       article.Title,
		CoverImage:  article.CoverImage,
		Author:      article.Author,
		Heading:     article.Heading,
		Content:     article.Content,
		PublishedAt: createdAt,
		UpdatedAt:   updatedAt,
	}

	cacheKey := fmt.Sprintf("article:%s", article.ID)
	cacheData := map[string]interface{}{
		"data": articleResponseDTO,
	}
	if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
		fmt.Printf("Error caching article to Redis: %v\n", err)
	}

	articles, total, err := s.ArticleRepo.FindAllArticles(0, 0)
	if err != nil {
		fmt.Printf("Error fetching all articles: %v\n", err)
	}

	var articleDTOs []dto.ArticleResponseDTO
	for _, a := range articles {
		createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
		updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)

		articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
			ID:          a.ID,
			Title:       a.Title,
			CoverImage:  a.CoverImage,
			Author:      a.Author,
			Heading:     a.Heading,
			Content:     a.Content,
			PublishedAt: createdAt,
			UpdatedAt:   updatedAt,
		})
	}

	articlesCacheKey := "articles:all"
	cacheData = map[string]interface{}{
		"data":  articleDTOs,
		"total": total,
	}
	if err := utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24); err != nil {
		fmt.Printf("Error caching all articles to Redis: %v\n", err)
	}

	return articleResponseDTO, nil
}

func (s *articleService) GetAllArticles(page, limit int) ([]dto.ArticleResponseDTO, int, error) {
	var cacheKey string

	if page == 0 && limit == 0 {
		cacheKey = "articles:all"
		cachedData, err := utils.GetJSONData(cacheKey)
		if err == nil && cachedData != nil {
			if data, ok := cachedData["data"].([]interface{}); ok {
				var articles []dto.ArticleResponseDTO
				for _, item := range data {
					articleData, ok := item.(map[string]interface{})
					if ok {
						articles = append(articles, dto.ArticleResponseDTO{
							ID:          articleData["article_id"].(string),
							Title:       articleData["title"].(string),
							CoverImage:  articleData["coverImage"].(string),
							Author:      articleData["author"].(string),
							Heading:     articleData["heading"].(string),
							Content:     articleData["content"].(string),
							PublishedAt: articleData["publishedAt"].(string),
							UpdatedAt:   articleData["updatedAt"].(string),
						})
					}
				}

				if total, ok := cachedData["total"].(float64); ok {
					fmt.Printf("Cached Total Articles: %f\n", total)
					return articles, int(total), nil
				} else {
					fmt.Println("Total articles not found in cache, using 0 as fallback.")
					return articles, 0, nil
				}
			}
		}
	}

	articles, total, err := s.ArticleRepo.FindAllArticles(page, limit)
	if err != nil {
		return nil, 0, fmt.Errorf("failed to fetch articles: %v", err)
	}

	fmt.Printf("Total Articles from Database: %d\n", total)

	var articleDTOs []dto.ArticleResponseDTO
	for _, article := range articles {
		publishedAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
		updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)

		articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
			ID:          article.ID,
			Title:       article.Title,
			CoverImage:  article.CoverImage,
			Author:      article.Author,
			Heading:     article.Heading,
			Content:     article.Content,
			PublishedAt: publishedAt,
			UpdatedAt:   updatedAt,
		})
	}

	cacheKey = fmt.Sprintf("articles_page:%d_limit:%d", page, limit)
	cacheData := map[string]interface{}{
		"data":  articleDTOs,
		"total": total,
	}

	fmt.Printf("Setting cache with total: %d\n", total)
	if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
		fmt.Printf("Error caching articles to Redis: %v\n", err)
	}

	return articleDTOs, total, nil
}

func (s *articleService) GetArticleByID(id string) (*dto.ArticleResponseDTO, error) {

	cacheKey := fmt.Sprintf("article:%s", id)
	cachedData, err := utils.GetJSONData(cacheKey)
	if err == nil && cachedData != nil {
		articleResponse := &dto.ArticleResponseDTO{}
		if data, ok := cachedData["data"].(string); ok {
			if err := json.Unmarshal([]byte(data), articleResponse); err == nil {
				return articleResponse, nil
			}
		}
	}

	article, err := s.ArticleRepo.FindArticleByID(id)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch article by ID: %v", err)
	}

	createdAt, _ := utils.FormatDateToIndonesianFormat(article.PublishedAt)
	updatedAt, _ := utils.FormatDateToIndonesianFormat(article.UpdatedAt)

	articleResponseDTO := &dto.ArticleResponseDTO{
		ID:          article.ID,
		Title:       article.Title,
		CoverImage:  article.CoverImage,
		Author:      article.Author,
		Heading:     article.Heading,
		Content:     article.Content,
		PublishedAt: createdAt,
		UpdatedAt:   updatedAt,
	}

	cacheData := map[string]interface{}{
		"data": articleResponseDTO,
	}
	if err := utils.SetJSONData(cacheKey, cacheData, time.Hour*24); err != nil {
		fmt.Printf("Error caching article to Redis: %v\n", err)
	}

	return articleResponseDTO, nil
}

func (s *articleService) UpdateArticle(id string, request dto.RequestArticleDTO, coverImage *multipart.FileHeader) (*dto.ArticleResponseDTO, error) {
	article, err := s.ArticleRepo.FindArticleByID(id)
	if err != nil {
		return nil, fmt.Errorf("article not found: %v", id)
	}

	if article.CoverImage != "" {
		err := deleteCoverArticle(article.CoverImage)
		if err != nil {
			return nil, fmt.Errorf("failed to delete old image: %v", err)
		}
	}

	var coverArticlePath string
	if coverImage != nil {
		coverArticlePath, err = s.saveCoverArticle(coverImage)
		if err != nil {
			return nil, fmt.Errorf("failed to save card photo: %v", err)
		}
	}

	if coverArticlePath != "" {
		article.CoverImage = coverArticlePath
	}

	article.Title = request.Title
	article.Heading = request.Heading
	article.Content = request.Content
	article.Author = request.Author

	err = s.ArticleRepo.UpdateArticle(id, article)
	if err != nil {
		return nil, fmt.Errorf("failed to update article: %v", err)
	}

	updatedArticle, err := s.ArticleRepo.FindArticleByID(id)
	if err != nil {
		return nil, fmt.Errorf("failed to fetch updated article: %v", err)
	}

	createdAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.PublishedAt)
	updatedAt, _ := utils.FormatDateToIndonesianFormat(updatedArticle.UpdatedAt)

	articleResponseDTO := &dto.ArticleResponseDTO{
		ID:          updatedArticle.ID,
		Title:       updatedArticle.Title,
		CoverImage:  updatedArticle.CoverImage,
		Author:      updatedArticle.Author,
		Heading:     updatedArticle.Heading,
		Content:     updatedArticle.Content,
		PublishedAt: createdAt,
		UpdatedAt:   updatedAt,
	}

	articleCacheKey := fmt.Sprintf("article:%s", updatedArticle.ID)
	err = utils.SetJSONData(articleCacheKey, map[string]interface{}{"data": articleResponseDTO}, time.Hour*24)
	if err != nil {
		fmt.Printf("Error caching updated article to Redis: %v\n", err)
	}

	articlesCacheKey := "articles:all"
	err = utils.DeleteData(articlesCacheKey)
	if err != nil {
		fmt.Printf("Error deleting articles cache: %v\n", err)
	}

	articles, _, err := s.ArticleRepo.FindAllArticles(0, 0)
	if err != nil {
		fmt.Printf("Error fetching all articles: %v\n", err)
	} else {
		var articleDTOs []dto.ArticleResponseDTO
		for _, a := range articles {
			createdAt, _ := utils.FormatDateToIndonesianFormat(a.PublishedAt)
			updatedAt, _ := utils.FormatDateToIndonesianFormat(a.UpdatedAt)

			articleDTOs = append(articleDTOs, dto.ArticleResponseDTO{
				ID:          a.ID,
				Title:       a.Title,
				CoverImage:  a.CoverImage,
				Author:      a.Author,
				Heading:     a.Heading,
				Content:     a.Content,
				PublishedAt: createdAt,
				UpdatedAt:   updatedAt,
			})
		}

		cacheData := map[string]interface{}{
			"data": articleDTOs,
		}
		err = utils.SetJSONData(articlesCacheKey, cacheData, time.Hour*24)
		if err != nil {
			fmt.Printf("Error caching updated articles to Redis: %v\n", err)
		}
	}

	return articleResponseDTO, nil
}

func (s *articleService) DeleteArticle(id string) error {
	article, err := s.ArticleRepo.FindArticleByID(id)
	if err != nil {
		return fmt.Errorf("failed to find article: %v", id)
	}

	if err := deleteCoverArticle(article.CoverImage); err != nil {
		return fmt.Errorf("error waktu menghapus cover image article %s: %v", id, err)
	}

	err = s.ArticleRepo.DeleteArticle(id)
	if err != nil {
		return fmt.Errorf("failed to delete article: %v", err)
	}

	articleCacheKey := fmt.Sprintf("article:%s", id)
	err = utils.DeleteData(articleCacheKey)
	if err != nil {
		fmt.Printf("Error deleting cache for article: %v\n", err)
	}

	articlesCacheKey := "articles:all"
	err = utils.DeleteData(articlesCacheKey)
	if err != nil {
		fmt.Printf("Error deleting cache for all articles: %v\n", err)
	}

	return nil
}
