package repository

import (
	"fmt"
	"jaecoo-be/app/database"
	"jaecoo-be/app/database/entity"
	"jaecoo-be/app/module/articles/request"
	"jaecoo-be/app/module/articles/response"
	"jaecoo-be/utils/paginator"
	utilSvc "jaecoo-be/utils/service"
	"strings"
	"time"

	"github.com/rs/zerolog"
)

type articlesRepository struct {
	DB  *database.Database
	Log zerolog.Logger
}

// ArticlesRepository define interface of IArticlesRepository
type ArticlesRepository interface {
	GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error)
	GetAllPublishSchedule() (articless []*entity.Articles, err error)
	FindOne(id uint) (articles *entity.Articles, err error)
	FindByFilename(thumbnailName string) (articleReturn *entity.Articles, err error)
	FindByOldId(oldId uint) (articles *entity.Articles, err error)
	Create(articles *entity.Articles) (articleReturn *entity.Articles, err error)
	Update(id uint, articles *entity.Articles) (err error)
	UpdateSkipNull(id uint, articles *entity.Articles) (err error)
	Delete(id uint) (err error)
	SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error)
	ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
	ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error)
}

func NewArticlesRepository(db *database.Database, log zerolog.Logger) ArticlesRepository {
	return &articlesRepository{
		DB:  db,
		Log: log,
	}
}

// implement interface of IArticlesRepository
func (_i *articlesRepository) GetAll(req request.ArticlesQueryRequest) (articless []*entity.Articles, paging paginator.Pagination, err error) {
	var count int64

	query := _i.DB.DB.Model(&entity.Articles{})

	if req.CategoryId != nil {
		query = query.Joins("JOIN article_category_details acd ON acd.article_id = articles.id").
			Where("acd.category_id = ?", req.CategoryId)
	}
	query = query.Where("articles.is_active = ?", true)

	if req.Title != nil && *req.Title != "" {
		title := strings.ToLower(*req.Title)
		query = query.Where("LOWER(articles.title) LIKE ?", "%"+strings.ToLower(title)+"%")
	}
	if req.Description != nil && *req.Description != "" {
		description := strings.ToLower(*req.Description)
		query = query.Where("LOWER(articles.description) LIKE ?", "%"+strings.ToLower(description)+"%")
	}
	if req.Tags != nil && *req.Tags != "" {
		tags := strings.ToLower(*req.Tags)
		query = query.Where("LOWER(articles.tags) LIKE ?", "%"+strings.ToLower(tags)+"%")
	}
	if req.TypeId != nil {
		query = query.Where("articles.type_id = ?", req.TypeId)
	}
	if req.IsPublish != nil {
		query = query.Where("articles.is_publish = ?", req.IsPublish)
	}
	if req.IsBanner != nil {
		query = query.Where("articles.is_banner = ?", req.IsBanner)
	}
	if req.IsDraft != nil {
		query = query.Where("articles.is_draft = ?", req.IsDraft)
	}
	if req.StatusId != nil {
		query = query.Where("articles.status_id = ?", req.StatusId)
	}
	if req.CreatedById != nil {
		query = query.Where("articles.created_by_id = ?", req.CreatedById)
	}
	query.Count(&count)

	if req.Pagination.SortBy != "" {
		direction := "ASC"
		if req.Pagination.Sort == "desc" {
			direction = "DESC"
		}
		query.Order(fmt.Sprintf("%s %s", req.Pagination.SortBy, direction))
	} else {
		direction := "DESC"
		sortBy := "articles.created_at"
		query.Order(fmt.Sprintf("%s %s", sortBy, direction))
	}

	req.Pagination.Count = count
	req.Pagination = paginator.Paging(req.Pagination)

	err = query.Offset(req.Pagination.Offset).Limit(req.Pagination.Limit).Find(&articless).Error
	if err != nil {
		return
	}

	paging = *req.Pagination

	return
}

func (_i *articlesRepository) GetAllPublishSchedule() (articles []*entity.Articles, err error) {
	query := _i.DB.DB.Where("publish_schedule IS NOT NULL")
	err = query.Find(&articles).Error
	if err != nil {
		return nil, err
	}
	return articles, nil
}

func (_i *articlesRepository) FindOne(id uint) (articles *entity.Articles, err error) {
	query := _i.DB.DB
	if err := query.First(&articles, id).Error; err != nil {
		return nil, err
	}

	return articles, nil
}

func (_i *articlesRepository) FindByFilename(thumbnailName string) (articles *entity.Articles, err error) {
	query := _i.DB.DB.Where("thumbnail_name = ?", thumbnailName)

	if err := query.First(&articles).Error; err != nil {
		return nil, err
	}

	return articles, nil
}

func (_i *articlesRepository) FindByOldId(oldId uint) (articles *entity.Articles, err error) {
	query := _i.DB.DB.Where("old_id = ?", oldId)

	if err := query.First(&articles).Error; err != nil {
		return nil, err
	}

	return articles, nil
}

func (_i *articlesRepository) Create(articles *entity.Articles) (articleReturn *entity.Articles, err error) {
	result := _i.DB.DB.Create(articles)
	return articles, result.Error
}

func (_i *articlesRepository) Update(id uint, articles *entity.Articles) (err error) {
	articlesMap, err := utilSvc.StructToMap(articles)
	if err != nil {
		return err
	}
	return _i.DB.DB.Model(&entity.Articles{}).
		Where(&entity.Articles{ID: id}).
		Updates(articlesMap).Error
}

func (_i *articlesRepository) UpdateSkipNull(id uint, articles *entity.Articles) (err error) {
	return _i.DB.DB.Model(&entity.Articles{}).
		Where(&entity.Articles{ID: id}).
		Updates(articles).Error
}

func (_i *articlesRepository) Delete(id uint) error {
	return _i.DB.DB.Delete(&entity.Articles{}, id).Error
}

func (_i *articlesRepository) SummaryStats(userID uint) (articleSummaryStats *response.ArticleSummaryStats, err error) {
	now := time.Now()
	startOfDay := now.Truncate(24 * time.Hour)
	startOfWeek := now.AddDate(0, 0, -int(now.Weekday())+1).Truncate(24 * time.Hour)

	// Query
	query := _i.DB.DB.Model(&entity.Articles{}).
		Select(
			"COUNT(*) AS total_all, "+
				"COALESCE(SUM(view_count), 0) AS total_views, "+
				"COALESCE(SUM(share_count), 0) AS total_shares, "+
				"COALESCE(SUM(comment_count), 0) AS total_comments, "+
				"COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_today, "+
				"COUNT(CASE WHEN created_at >= ? THEN 1 END) AS total_this_week",
			startOfDay, startOfWeek).
		Where("created_by_id = ?", userID)

	err = query.Scan(&articleSummaryStats).Error

	return articleSummaryStats, err
}

func (_i *articlesRepository) ArticlePerUserLevelStats(userLevelId *uint, levelNumber *int, startDate *time.Time, endDate *time.Time) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {

	levelNumberTop := 1

	query := _i.DB.DB.Model(&entity.Articles{}).
		Select("user_levels.id as user_level_id", "user_levels.name as user_level_name", "COUNT(articles.id) as total_article").
		Joins("LEFT JOIN users ON articles.created_by_id = users.id").
		Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
		Where("articles.is_active = true")

	if userLevelId != nil && *levelNumber != levelNumberTop {
		query = query.Where("user_levels.id = ? or user_levels.parent_level_id = ?", *userLevelId, *userLevelId)
	} else {
		query = _i.DB.DB.Raw(`  
			WITH LevelHierarchy AS (  
				SELECT   
					id,   
					name,   
					level_number,  
					parent_level_id,  
					CASE
						WHEN level_number = 1 THEN id
						WHEN level_number = 2 and name ILIKE '%polda%' THEN id
						WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN parent_level_id
						WHEN level_number = 3 THEN parent_level_id  
					END AS level_2_id,  
					CASE   
						WHEN level_number = 1 THEN name  
						WHEN level_number = 2 and name ILIKE '%polda%' THEN name
						WHEN level_number = 2 and name NOT ILIKE '%polda%' THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id) 
						WHEN level_number = 3 THEN (SELECT name FROM user_levels ul2 WHERE ul2.id = user_levels.parent_level_id)  
					END AS level_2_name  
				FROM user_levels  
			)  
			SELECT   
				lh.level_2_id AS user_level_id,  
				UPPER(lh.level_2_name) AS user_level_name,  
				COUNT(articles.id) AS total_article  
			FROM articles  
			JOIN users ON articles.created_by_id = users.id  
			JOIN LevelHierarchy lh ON users.user_level_id = lh.id  
			WHERE articles.is_active = true AND lh.level_2_id > 0`)

		query = query.Group("lh.level_2_id, lh.level_2_name").Order("total_article DESC")
	}

	// Apply date filters if provided
	if startDate != nil {
		query = query.Where("articles.created_at >= ?", *startDate)
	}
	if endDate != nil {
		query = query.Where("articles.created_at <= ?", *endDate)
	}

	// Group by all non-aggregated columns
	err = query.Group("user_levels.id, user_levels.name").
		Order("total_article DESC").
		Scan(&articlePerUserLevelStats).Error

	return articlePerUserLevelStats, err
}

func (_i *articlesRepository) ArticleMonthlyStats(userLevelId *uint, levelNumber *int, year int) (articleMontlyStats []*response.ArticleMonthlyStats, err error) {
	levelNumberTop := 1

	if year < 1900 || year > 2100 {
		return nil, fmt.Errorf("invalid year")
	}

	var results []struct {
		Month        int
		Day          int
		TotalView    int
		TotalComment int
		TotalShare   int
	}

	query := _i.DB.DB.Model(&entity.Articles{}).
		Select("EXTRACT(MONTH FROM created_at) as month, EXTRACT(DAY FROM created_at) as day, "+
			"SUM(view_count) as total_view, "+
			"SUM(comment_count) as total_comment, "+
			"SUM(share_count) as total_share").
		Where("EXTRACT(YEAR FROM created_at) = ?", year)

	if userLevelId != nil && *levelNumber != levelNumberTop {
		query = _i.DB.DB.Model(&entity.Articles{}).
			Select("EXTRACT(MONTH FROM articles.created_at) as month, EXTRACT(DAY FROM articles.created_at) as day, "+
				"SUM(articles.view_count) as total_view, "+
				"SUM(articles.comment_count) as total_comment, "+
				"SUM(articles.share_count) as total_share").
			Joins("LEFT JOIN users ON articles.created_by_id = users.id").
			Joins("LEFT JOIN user_levels ON users.user_level_id = user_levels.id").
			Where("articles.is_active = true").
			Where("EXTRACT(YEAR FROM articles.created_at) = ?", year).
			Where("(user_levels.id = ? OR user_levels.parent_level_id = ?)", *userLevelId, *userLevelId)

	}

	err = query.Group("month, day").Scan(&results).Error
	if err != nil {
		return nil, err
	}

	// Siapkan struktur untuk menyimpan data bulanan
	monthlyAnalytics := make([]*response.ArticleMonthlyStats, 12)
	for i := 0; i < 12; i++ {
		daysInMonth := time.Date(year, time.Month(i+1), 0, 0, 0, 0, 0, time.UTC).Day()
		monthlyAnalytics[i] = &response.ArticleMonthlyStats{
			Year:    year,
			Month:   i + 1,
			View:    make([]int, daysInMonth),
			Comment: make([]int, daysInMonth),
			Share:   make([]int, daysInMonth),
		}
	}

	// Isi data dari hasil agregasi
	for _, result := range results {
		monthIndex := result.Month - 1
		dayIndex := result.Day - 1

		if monthIndex >= 0 && monthIndex < 12 {
			if dayIndex >= 0 && dayIndex < len(monthlyAnalytics[monthIndex].View) {
				monthlyAnalytics[monthIndex].View[dayIndex] = result.TotalView
				monthlyAnalytics[monthIndex].Comment[dayIndex] = result.TotalComment
				monthlyAnalytics[monthIndex].Share[dayIndex] = result.TotalShare
			}
		}
	}

	return monthlyAnalytics, nil
}
