package service

import (
	"context"
	"errors"
	"fmt"
	"io"
	"log"
	"math/rand"
	"mime"
	"path/filepath"
	"strconv"
	"strings"
	"time"
	"web-qudo-be/app/database/entity"
	approvalWorkflowsRepository "web-qudo-be/app/module/approval_workflows/repository"
	articleApprovalFlowsRepository "web-qudo-be/app/module/article_approval_flows/repository"
	articleApprovalsRepository "web-qudo-be/app/module/article_approvals/repository"
	articleCategoriesRepository "web-qudo-be/app/module/article_categories/repository"
	articleCategoryDetailsRepository "web-qudo-be/app/module/article_category_details/repository"
	articleCategoryDetailsReq "web-qudo-be/app/module/article_category_details/request"
	articleFilesRepository "web-qudo-be/app/module/article_files/repository"
	"web-qudo-be/app/module/articles/mapper"
	"web-qudo-be/app/module/articles/repository"
	"web-qudo-be/app/module/articles/request"
	"web-qudo-be/app/module/articles/response"
	usersRepository "web-qudo-be/app/module/users/repository"
	config "web-qudo-be/config/config"
	minioStorage "web-qudo-be/config/config"
	"web-qudo-be/utils/paginator"
	utilSvc "web-qudo-be/utils/service"

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

// ArticlesService
type articlesService struct {
	Repo                       repository.ArticlesRepository
	ArticleCategoriesRepo      articleCategoriesRepository.ArticleCategoriesRepository
	ArticleFilesRepo           articleFilesRepository.ArticleFilesRepository
	ArticleApprovalsRepo       articleApprovalsRepository.ArticleApprovalsRepository
	ArticleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository
	Log                        zerolog.Logger
	Cfg                        *config.Config
	UsersRepo                  usersRepository.UsersRepository
	MinioStorage               *minioStorage.MinioStorage

	// Dynamic approval system dependencies
	ArticleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository
	ApprovalWorkflowsRepo    approvalWorkflowsRepository.ApprovalWorkflowsRepository
}

// ArticlesService define interface of IArticlesService
type ArticlesService interface {
	All(clientId *uuid.UUID, authToken string, req request.ArticlesQueryRequest) (articles []*response.ArticlesResponse, paging paginator.Pagination, err error)
	Show(clientId *uuid.UUID, id uint) (articles *response.ArticlesResponse, err error)
	ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error)
	ShowBySlug(clientId *uuid.UUID, slug string) (articles *response.ArticlesResponse, err error)
	Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error)
	SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error)
	Update(clientId *uuid.UUID, id uint, req request.ArticlesUpdateRequest) (err error)
	Delete(clientId *uuid.UUID, id uint) error
	UpdateActivityCount(clientId *uuid.UUID, id uint, activityTypeId int) (err error)
	UpdateApproval(clientId *uuid.UUID, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error)
	UpdateBanner(clientId *uuid.UUID, id uint, isBanner bool) error
	Viewer(clientId *uuid.UUID, c *fiber.Ctx) error
	SummaryStats(clientId *uuid.UUID, authToken string) (summaryStats *response.ArticleSummaryStats, err error)
	ArticlePerUserLevelStats(clientId *uuid.UUID, authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error)
	ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error)
	PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error
	ExecuteScheduling() error

	// Dynamic approval system methods
	SubmitForApproval(clientId *uuid.UUID, articleId uint, authToken string, workflowId *uint) error
	GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error)
	GetArticlesWaitingForApproval(clientId *uuid.UUID, authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error)
	GetPendingApprovals(clientId *uuid.UUID, authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) // Updated with typeId filter

	// No-approval system methods
	CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error)
	AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error
	GetClientApprovalSettings(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error)
	SetArticleApprovalExempt(clientId *uuid.UUID, articleId uint, exempt bool, reason string) error

	// Publish/Unpublish methods
	Publish(clientId *uuid.UUID, articleId uint, authToken string) error
	Unpublish(clientId *uuid.UUID, articleId uint, authToken string) error
}

// NewArticlesService init ArticlesService
func NewArticlesService(
	repo repository.ArticlesRepository,
	articleCategoriesRepo articleCategoriesRepository.ArticleCategoriesRepository,
	articleCategoryDetailsRepo articleCategoryDetailsRepository.ArticleCategoryDetailsRepository,
	articleFilesRepo articleFilesRepository.ArticleFilesRepository,
	articleApprovalsRepo articleApprovalsRepository.ArticleApprovalsRepository,
	articleApprovalFlowsRepo articleApprovalFlowsRepository.ArticleApprovalFlowsRepository,
	approvalWorkflowsRepo approvalWorkflowsRepository.ApprovalWorkflowsRepository,
	log zerolog.Logger,
	cfg *config.Config,
	usersRepo usersRepository.UsersRepository,
	minioStorage *minioStorage.MinioStorage,
) ArticlesService {

	return &articlesService{
		Repo:                       repo,
		ArticleCategoriesRepo:      articleCategoriesRepo,
		ArticleCategoryDetailsRepo: articleCategoryDetailsRepo,
		ArticleFilesRepo:           articleFilesRepo,
		ArticleApprovalsRepo:       articleApprovalsRepo,
		ArticleApprovalFlowsRepo:   articleApprovalFlowsRepo,
		ApprovalWorkflowsRepo:      approvalWorkflowsRepo,
		Log:                        log,
		UsersRepo:                  usersRepo,
		MinioStorage:               minioStorage,
		Cfg:                        cfg,
	}
}

// All implement interface of ArticlesService
func (_i *articlesService) All(clientId *uuid.UUID, authToken string, req request.ArticlesQueryRequest) (articless []*response.ArticlesResponse, paging paginator.Pagination, err error) {
	// Extract userLevelId from authToken
	var userLevelId *uint
	if authToken != "" {
		user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
		if user != nil {
			userLevelId = &user.UserLevelId
			_i.Log.Info().Interface("userLevelId", userLevelId).Msg("Extracted userLevelId from auth token")
		}
	}

	if req.Category != nil {
		findCategory, err := _i.ArticleCategoriesRepo.FindOneBySlug(clientId, *req.Category)
		if err != nil {
			return nil, paging, err
		}
		req.CategoryId = &findCategory.ID
	}

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

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:articlesService", "Methods:All").
		Interface("results", results).Msg("")

	host := _i.Cfg.App.Domain

	for _, result := range results {
		articleRes := mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo)
		articless = append(articless, articleRes)
	}

	return
}

func (_i *articlesService) Show(clientId *uuid.UUID, id uint) (articles *response.ArticlesResponse, err error) {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return nil, err
	}

	host := _i.Cfg.App.Domain

	return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil
}

func (_i *articlesService) ShowByOldId(clientId *uuid.UUID, oldId uint) (articles *response.ArticlesResponse, err error) {
	result, err := _i.Repo.FindByOldId(clientId, oldId)
	if err != nil {
		return nil, err
	}

	host := _i.Cfg.App.Domain

	return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil
}

func (_i *articlesService) ShowBySlug(clientId *uuid.UUID, slug string) (articles *response.ArticlesResponse, err error) {
	result, err := _i.Repo.FindBySlug(clientId, slug)
	if err != nil {
		return nil, err
	}

	host := _i.Cfg.App.Domain

	return mapper.ArticlesResponseMapper(_i.Log, host, clientId, result, _i.ArticleCategoriesRepo, _i.ArticleCategoryDetailsRepo, _i.ArticleFilesRepo, _i.UsersRepo), nil
}

func (_i *articlesService) Save(clientId *uuid.UUID, req request.ArticlesCreateRequest, authToken string) (articles *entity.Articles, err error) {
	_i.Log.Info().Interface("data", req).Msg("")
	newReq := req.ToEntity()

	var userLevelNumber int
	var approvalLevelId int
	if req.CreatedById != nil {
		createdBy, err := _i.UsersRepo.FindOne(clientId, *req.CreatedById)
		if err != nil {
			return nil, fmt.Errorf("User not found")
		}
		newReq.CreatedById = &createdBy.ID
		userLevelNumber = createdBy.UserLevel.LevelNumber

		// Find the next higher level for approval (level_number should be smaller)
		approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
	} else {
		createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
		newReq.CreatedById = &createdBy.ID
		userLevelNumber = createdBy.UserLevel.LevelNumber

		// Find the next higher level for approval (level_number should be smaller)
		approvalLevelId = _i.findNextApprovalLevel(clientId, userLevelNumber)
	}

	isDraft := true
	if req.IsDraft == &isDraft {
		draftedAt := time.Now()
		newReq.IsDraft = &isDraft
		newReq.DraftedAt = &draftedAt
		isPublishFalse := false
		newReq.IsPublish = &isPublishFalse
		newReq.PublishedAt = nil
	}

	isPublish := true
	if req.IsPublish == &isPublish {
		publishedAt := time.Now()
		newReq.IsPublish = &isPublish
		newReq.PublishedAt = &publishedAt
		isDraftFalse := false
		newReq.IsDraft = &isDraftFalse
		newReq.DraftedAt = nil
	}

	if req.CreatedAt != nil {
		layout := "2006-01-02 15:04:05"
		parsedTime, err := time.Parse(layout, *req.CreatedAt)
		if err != nil {
			return nil, fmt.Errorf("Error parsing time:", err)
		}
		newReq.CreatedAt = parsedTime
	}

	// Dynamic Approval Workflow System
	statusIdOne := 1
	statusIdTwo := 2
	isPublishFalse := false

	// Get user info for approval logic
	createdBy := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)

	// Check if user level requires approval
	if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == false {
		// User level doesn't require approval - auto publish
		newReq.NeedApprovalFrom = nil
		newReq.StatusId = &statusIdTwo
		newReq.IsPublish = &isPublishFalse
		newReq.PublishedAt = nil
		newReq.BypassApproval = &[]bool{true}[0]
	} else {
		// User level requires approval - set to pending
		newReq.NeedApprovalFrom = &approvalLevelId
		newReq.StatusId = &statusIdOne
		newReq.IsPublish = &isPublishFalse
		newReq.PublishedAt = nil
		newReq.BypassApproval = &[]bool{false}[0]
	}

	saveArticleRes, err := _i.Repo.Create(clientId, newReq)
	if err != nil {
		return nil, err
	}

	// Dynamic Approval Workflow Assignment
	if createdBy != nil && *createdBy.UserLevel.IsApprovalActive == true {
		// Get default workflow for the client
		defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId)
		if err == nil && defaultWorkflow != nil {
			// Assign workflow to article
			saveArticleRes.WorkflowId = &defaultWorkflow.ID
			saveArticleRes.CurrentApprovalStep = &[]int{1}[0] // Start at step 1

			// Update article with workflow info
			err = _i.Repo.Update(clientId, saveArticleRes.ID, saveArticleRes)
			if err != nil {
				_i.Log.Error().Err(err).Msg("Failed to update article with workflow")
			}

			// Create approval flow
			approvalFlow := &entity.ArticleApprovalFlows{
				ArticleId:     saveArticleRes.ID,
				WorkflowId:    defaultWorkflow.ID,
				CurrentStep:   1,
				StatusId:      1, // In Progress
				SubmittedById: *newReq.CreatedById,
				SubmittedAt:   time.Now(),
				ClientId:      clientId,
			}

			_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
			if err != nil {
				_i.Log.Error().Err(err).Msg("Failed to create approval flow")
			}
		}

		// Create legacy approval record for backward compatibility
		articleApproval := &entity.ArticleApprovals{
			ArticleId:       saveArticleRes.ID,
			ApprovalBy:      *newReq.CreatedById,
			StatusId:        statusIdOne,
			Message:         "Need Approval",
			ApprovalAtLevel: &approvalLevelId,
		}
		_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
		if err != nil {
			_i.Log.Error().Err(err).Msg("Failed to create legacy approval record")
		}
	} else {
		// Auto-publish for users who don't require approval
		articleApproval := &entity.ArticleApprovals{
			ArticleId:       saveArticleRes.ID,
			ApprovalBy:      *newReq.CreatedById,
			StatusId:        statusIdTwo,
			Message:         "Publish Otomatis",
			ApprovalAtLevel: nil,
		}
		_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
		if err != nil {
			_i.Log.Error().Err(err).Msg("Failed to create auto-approval record")
		}
	}

	var categoryIds []string
	if req.CategoryIds != "" {
		categoryIds = strings.Split(req.CategoryIds, ",")
	}

	_i.Log.Info().Interface("categoryIds", categoryIds).Msg("")

	for _, categoryId := range categoryIds {
		categoryIdInt, _ := strconv.Atoi(categoryId)

		_i.Log.Info().Interface("categoryIdUint", uint(categoryIdInt)).Msg("")

		findCategory, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(categoryIdInt))

		_i.Log.Info().Interface("findCategory", findCategory).Msg("")

		if err != nil {
			return nil, err
		}

		if findCategory == nil {
			return nil, errors.New("category not found")
		}

		categoryReq := articleCategoryDetailsReq.ArticleCategoryDetailsCreateRequest{
			ArticleId:  saveArticleRes.ID,
			CategoryId: categoryIdInt,
			IsActive:   true,
		}
		newCategoryReq := categoryReq.ToEntity()

		err = _i.ArticleCategoryDetailsRepo.Create(newCategoryReq)
		if err != nil {
			return nil, err
		}
	}

	return saveArticleRes, nil
}

func (_i *articlesService) SaveThumbnail(clientId *uuid.UUID, c *fiber.Ctx) (err error) {

	id, err := strconv.ParseUint(c.Params("id"), 10, 0)
	if err != nil {
		return err
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:articlesService", "Methods:SaveThumbnail").
		Interface("id", id).Msg("")

	bucketName := _i.MinioStorage.Cfg.ObjectStorage.MinioStorage.BucketName

	form, err := c.MultipartForm()
	if err != nil {
		return err
	}
	files := form.File["files"]

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

	// Iterasi semua file yang diunggah
	for _, file := range files {

		_i.Log.Info().Str("timestamp", time.Now().
			Format(time.RFC3339)).Str("Service:Resource", "Uploader:: loop1").
			Interface("data", file).Msg("")

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

		filename := filepath.Base(file.Filename)
		filename = strings.ReplaceAll(filename, " ", "")
		filenameWithoutExt := filepath.Clean(filename[:len(filename)-len(filepath.Ext(filename))])
		extension := filepath.Ext(file.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("articles/thumbnail/%d/%d/%s", now.Year(), now.Month(), newFilename)

		findCategory, err := _i.Repo.FindOne(clientId, uint(id))
		findCategory.ThumbnailName = &newFilename
		findCategory.ThumbnailPath = &objectName
		err = _i.Repo.Update(clientId, uint(id), findCategory)
		if err != nil {
			return err
		}

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

	return
}

func (_i *articlesService) Update(clientId *uuid.UUID, id uint, req request.ArticlesUpdateRequest) (err error) {
	_i.Log.Info().Interface("data", req).Msg("")
	newReq := req.ToEntity()

	if req.CreatedAt != nil {
		layout := "2006-01-02 15:04:05"
		parsedTime, err := time.Parse(layout, *req.CreatedAt)
		if err != nil {
			return fmt.Errorf("Error parsing time:", err)
		}
		newReq.CreatedAt = parsedTime
	}

	return _i.Repo.UpdateSkipNull(clientId, id, newReq)
}

func (_i *articlesService) Delete(clientId *uuid.UUID, id uint) error {
	return _i.Repo.Delete(clientId, id)
}

func (_i *articlesService) Viewer(clientId *uuid.UUID, c *fiber.Ctx) (err error) {
	thumbnailName := c.Params("thumbnailName")

	emptyImage := "empty-image.jpg"
	searchThumbnail := emptyImage
	if thumbnailName != emptyImage {
		result, err := _i.Repo.FindByFilename(clientId, thumbnailName)
		if err != nil {
			return err
		}
		_i.Log.Info().Str("timestamp", time.Now().
			Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer").
			Interface("resultThumbnail", result.ThumbnailPath).Msg("")

		if result.ThumbnailPath != nil {
			searchThumbnail = *result.ThumbnailPath
		} else {
			searchThumbnail = "articles/thumbnail/" + emptyImage
		}
	} else {
		searchThumbnail = "articles/thumbnail/" + emptyImage
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "articlesService:Viewer").
		Interface("searchThumbnail", searchThumbnail).Msg("")

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

	// Create minio connection.
	minioClient, err := _i.MinioStorage.ConnectMinio()
	if err != nil {
		// Return status 500 and minio connection error.
		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 {
		log.Fatalln(err)
	}
	defer fileContent.Close()

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

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

	if _, err := io.Copy(c.Response().BodyWriter(), fileContent); err != nil {
		return err
	}

	return
}

func (_i *articlesService) UpdateActivityCount(clientId *uuid.UUID, id uint, activityTypeId int) error {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}

	viewCount := 0
	if result.ViewCount != nil {
		viewCount = *result.ViewCount
	}
	shareCount := 0
	if result.ShareCount != nil {
		shareCount = *result.ShareCount
	}
	commentCount := 0
	if result.CommentCount != nil {
		commentCount = *result.CommentCount
	}

	if activityTypeId == 2 {
		viewCount++
	} else if activityTypeId == 3 {
		shareCount++
	} else if activityTypeId == 4 {
		commentCount++
	}
	result.ViewCount = &viewCount
	result.ShareCount = &shareCount
	result.CommentCount = &commentCount
	return _i.Repo.Update(clientId, id, result)
}

func (_i *articlesService) SummaryStats(clientId *uuid.UUID, authToken string) (summaryStats *response.ArticleSummaryStats, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)

	result, err := _i.Repo.SummaryStats(clientId, user.ID)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (_i *articlesService) ArticlePerUserLevelStats(clientId *uuid.UUID, authToken string, startDate *string, endDate *string) (articlePerUserLevelStats []*response.ArticlePerUserLevelStats, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
		Interface("startDate", startDate).Msg("")
	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
		Interface("endDate", endDate).Msg("")

	var userLevelId *uint
	var userLevelNumber *int

	if user != nil {
		userLevelId = &user.UserLevelId
		userLevelNumber = &user.UserLevel.LevelNumber
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
		Interface("userLevelId", userLevelId).Msg("")
	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:Resource", "articlesService:ArticlePerUserLevelStats").
		Interface("userLevelNumber", userLevelNumber).Msg("")

	result, err := _i.Repo.ArticlePerUserLevelStats(clientId, userLevelId, userLevelNumber, nil, nil)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (_i *articlesService) ArticleMonthlyStats(clientId *uuid.UUID, authToken string, year *int) (articleMonthlyStats []*response.ArticleMonthlyStats, err error) {
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)

	var userLevelId *uint
	var userLevelNumber *int

	if user != nil {
		userLevelId = &user.UserLevelId
		userLevelNumber = &user.UserLevel.LevelNumber
	}

	result, err := _i.Repo.ArticleMonthlyStats(clientId, userLevelId, userLevelNumber, *year)
	if err != nil {
		return nil, err
	}
	return result, nil
}

func (_i *articlesService) UpdateApproval(clientId *uuid.UUID, id uint, statusId int, userLevelId int, userLevelNumber int, userParentLevelId int) (err error) {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}

	_i.Log.Info().Interface("statusId", statusId).Msg("")

	statusIdOne := 1
	statusIdTwo := 2
	statusIdThree := 3
	isPublish := true
	isDraftFalse := false

	if statusId == 2 {
		if userLevelNumber == 2 || userLevelNumber == 3 {
			result.NeedApprovalFrom = &userParentLevelId
			result.StatusId = &statusIdOne
		} else {
			result.NeedApprovalFrom = nil
			result.StatusId = &statusIdTwo

			result.IsPublish = &isPublish
			publishedAt := time.Now()
			result.PublishedAt = &publishedAt

			result.IsDraft = &isDraftFalse
			result.DraftedAt = nil
		}

		userLevelIdStr := strconv.Itoa(userLevelId)
		if result.HasApprovedBy == nil {
			result.HasApprovedBy = &userLevelIdStr
		} else {
			hasApprovedBySlice := strings.Split(*result.HasApprovedBy, ",")
			hasApprovedBySlice = append(hasApprovedBySlice, userLevelIdStr)
			hasApprovedByJoin := strings.Join(hasApprovedBySlice, ",")
			result.HasApprovedBy = &hasApprovedByJoin
		}
	} else if statusId == 3 {
		result.StatusId = &statusIdThree
		result.NeedApprovalFrom = nil
		result.HasApprovedBy = nil
	}

	err = _i.Repo.Update(clientId, id, result)
	if err != nil {
		return err
	}

	return
}

func (_i *articlesService) PublishScheduling(clientId *uuid.UUID, id uint, publishSchedule string) error {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}

	// Validate publish schedule format
	dateLayout := "2006-01-02"
	datetimeLayout := "2006-01-02 15:04:05"

	// Try parsing as datetime first (with time)
	_, parseErr := time.Parse(datetimeLayout, publishSchedule)
	if parseErr != nil {
		// If datetime parsing fails, try parsing as date only
		_, parseErr = time.Parse(dateLayout, publishSchedule)
		if parseErr != nil {
			return fmt.Errorf("invalid publish schedule format. Supported formats: '2006-01-02' or '2006-01-02 15:04:05'")
		}
	}

	result.PublishSchedule = &publishSchedule
	return _i.Repo.Update(clientId, id, result)
}

func (_i *articlesService) UpdateBanner(clientId *uuid.UUID, id uint, isBanner bool) error {
	result, err := _i.Repo.FindOne(clientId, id)
	if err != nil {
		return err
	}
	result.IsBanner = &isBanner
	return _i.Repo.Update(clientId, id, result)
}

func (_i *articlesService) ExecuteScheduling() error {
	// For background jobs, we don't have context, so pass nil for clientId
	articles, err := _i.Repo.GetAllPublishSchedule(nil)
	if err != nil {
		return err
	}

	// Support both date-only and datetime formats
	dateLayout := "2006-01-02"
	datetimeLayout := "2006-01-02 15:04:05"
	now := time.Now()

	for _, article := range articles { // Looping setiap artikel
		if article.PublishSchedule == nil {
			continue
		}

		var scheduledTime time.Time
		var parseErr error

		// Try parsing as datetime first (with time)
		scheduledTime, parseErr = time.Parse(datetimeLayout, *article.PublishSchedule)
		if parseErr != nil {
			// If datetime parsing fails, try parsing as date only
			scheduledTime, parseErr = time.Parse(dateLayout, *article.PublishSchedule)
			if parseErr != nil {
				_i.Log.Warn().Str("timestamp", time.Now().
					Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling").
					Str("Invalid schedule format", *article.PublishSchedule).
					Interface("Article ID", article.ID).Msg("")
				continue
			}
			// If parsed as date only, set time to start of day (00:00:00) in local timezone
			scheduledTime = time.Date(scheduledTime.Year(), scheduledTime.Month(), scheduledTime.Day(), 0, 0, 0, 0, now.Location())
		} else {
			// For datetime format, parse in local timezone
			scheduledTime = time.Date(scheduledTime.Year(), scheduledTime.Month(), scheduledTime.Day(),
				scheduledTime.Hour(), scheduledTime.Minute(), scheduledTime.Second(), 0, now.Location())
		}

		// Check if the scheduled time has passed (for datetime) or if it's today (for date only)
		shouldPublish := false
		if len(*article.PublishSchedule) > 10 { // Contains time (datetime format)
			// For datetime format, check if scheduled time has passed
			shouldPublish = now.After(scheduledTime) || now.Equal(scheduledTime)
		} else {
			// For date-only format, check if it's today
			today := now.Truncate(24 * time.Hour)
			scheduledDate := scheduledTime.Truncate(24 * time.Hour)
			shouldPublish = scheduledDate.Equal(today) || scheduledDate.Before(today)
		}

		if shouldPublish {
			isPublish := true
			statusIdTwo := 2

			article.PublishSchedule = nil
			article.IsPublish = &isPublish
			article.PublishedAt = &now
			article.StatusId = &statusIdTwo

			// For background jobs, we don't have context, so pass nil for clientId
			if err := _i.Repo.UpdateSkipNull(nil, article.ID, article); err != nil {
				_i.Log.Info().Str("timestamp", time.Now().
					Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling").
					Interface("Failed to publish Article ID : ", article.ID).Msg("")
			} else {
				_i.Log.Info().Str("timestamp", time.Now().
					Format(time.RFC3339)).Str("Service:articlesService", "Methods:ExecuteScheduling").
					Interface("Successfully published Article ID : ", article.ID).Msg("")
			}
		}
	}

	return err
}

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

	// jika tidak ada ekstensi, kembalikan string kosong
	if len(parts) == 1 || (len(parts) == 2 && parts[0] == "") {
		return ""
	}

	// ambil ekstensi terakhir
	return parts[len(parts)-1]
}

// SubmitForApproval submits an article for approval using the dynamic workflow system
func (_i *articlesService) SubmitForApproval(clientId *uuid.UUID, articleId uint, authToken string, workflowId *uint) error {
	// Extract user info from auth token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found from auth token")
	}

	// Check if article exists
	article, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return err
	}

	// If no workflow specified, get the default workflow
	if workflowId == nil {
		defaultWorkflow, err := _i.ApprovalWorkflowsRepo.GetDefault(clientId)
		if err != nil {
			return err
		}
		workflowId = &defaultWorkflow.ID
	}

	// Validate workflow exists and is active
	_, err = _i.ApprovalWorkflowsRepo.FindOne(clientId, *workflowId)
	if err != nil {
		return err
	}

	// Create approval flow
	approvalFlow := &entity.ArticleApprovalFlows{
		ArticleId:     articleId,
		WorkflowId:    *workflowId,
		CurrentStep:   1,
		StatusId:      1, // 1 = In Progress
		ClientId:      clientId,
		SubmittedById: user.ID,
	}

	_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
	if err != nil {
		return err
	}

	// Update article status to pending approval
	statusId := 1 // Pending approval
	article.StatusId = &statusId
	article.WorkflowId = workflowId

	err = _i.Repo.Update(clientId, articleId, article)
	if err != nil {
		return err
	}

	_i.Log.Info().Str("timestamp", time.Now().
		Format(time.RFC3339)).Str("Service:articlesService", "Methods:SubmitForApproval").
		Interface("Article submitted for approval", articleId).Msg("")

	return nil
}

// GetApprovalStatus gets the current approval status of an article
func (_i *articlesService) GetApprovalStatus(clientId *uuid.UUID, articleId uint) (*response.ArticleApprovalStatusResponse, error) {
	// Check if article exists
	_, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return nil, err
	}

	// Get approval flow
	approvalFlow, err := _i.ArticleApprovalFlowsRepo.FindByArticleId(clientId, articleId)
	if err != nil {
		// Article might not be in approval process
		return &response.ArticleApprovalStatusResponse{
			ArticleId:   articleId,
			Status:      "not_submitted",
			CurrentStep: 0,
			TotalSteps:  0,
			Progress:    0,
		}, nil
	}

	// Get workflow details
	workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, approvalFlow.WorkflowId)
	if err != nil {
		return nil, err
	}

	// Get workflow steps
	workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, approvalFlow.WorkflowId)
	if err != nil {
		return nil, err
	}

	totalSteps := len(workflowSteps)
	progress := 0.0
	if totalSteps > 0 {
		progress = float64(approvalFlow.CurrentStep-1) / float64(totalSteps) * 100
	}

	// Determine status
	status := "in_progress"
	if approvalFlow.StatusId == 2 {
		status = "approved"
	} else if approvalFlow.StatusId == 3 {
		status = "rejected"
	} else if approvalFlow.StatusId == 4 {
		status = "revision_requested"
	}

	// Get current approver info
	var currentApprover *string
	var nextStep *string
	if approvalFlow.CurrentStep <= totalSteps && approvalFlow.StatusId == 1 {
		if approvalFlow.CurrentStep < totalSteps {
			// Array indexing starts from 0, so subtract 1 from CurrentStep
			nextStepIndex := approvalFlow.CurrentStep - 1
			if nextStepIndex >= 0 && nextStepIndex < len(workflowSteps) {
				nextStepInfo := workflowSteps[nextStepIndex]
				nextStep = &nextStepInfo.RequiredUserLevel.Name
			}
		}
	}

	return &response.ArticleApprovalStatusResponse{
		ArticleId:       articleId,
		WorkflowId:      &workflow.ID,
		WorkflowName:    &workflow.Name,
		CurrentStep:     approvalFlow.CurrentStep,
		TotalSteps:      totalSteps,
		Status:          status,
		CurrentApprover: currentApprover,
		SubmittedAt:     &approvalFlow.CreatedAt,
		LastActionAt:    &approvalFlow.UpdatedAt,
		Progress:        progress,
		CanApprove:      false, // TODO: Implement based on user permissions
		NextStep:        nextStep,
	}, nil
}

// GetPendingApprovals gets articles pending approval for a specific user level
func (_i *articlesService) GetPendingApprovals(clientId *uuid.UUID, authToken string, page, limit int, typeId *int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) {
	// Extract user info from auth token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, paginator.Pagination{}, errors.New("user not found from auth token")
	}

	// Prepare filters
	filters := make(map[string]interface{})
	if typeId != nil {
		filters["type_id"] = *typeId
	}

	// Get pending approvals for the user level
	approvalFlows, paging, err := _i.ArticleApprovalFlowsRepo.GetPendingApprovals(clientId, user.UserLevelId, page, limit, filters)
	if err != nil {
		return nil, paging, err
	}

	var responses []*response.ArticleApprovalQueueResponse
	for _, flow := range approvalFlows {
		// Get article details
		article, err := _i.Repo.FindOne(clientId, flow.ArticleId)
		if err != nil {
			continue
		}

		// Get workflow details
		workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, flow.WorkflowId)
		if err != nil {
			continue
		}

		// Get workflow steps
		workflowSteps, err := _i.ApprovalWorkflowsRepo.GetWorkflowSteps(clientId, flow.WorkflowId)
		if err != nil {
			continue
		}

		// Calculate days in queue
		daysInQueue := int(time.Since(flow.CreatedAt).Hours() / 24)

		// Determine priority based on days in queue
		priority := "low"
		if daysInQueue > 7 {
			priority = "urgent"
		} else if daysInQueue > 3 {
			priority = "high"
		} else if daysInQueue > 1 {
			priority = "medium"
		}

		// Get author name
		var authorName string
		if article.CreatedById != nil {
			user, err := _i.UsersRepo.FindOne(clientId, *article.CreatedById)
			if err == nil && user != nil {
				authorName = user.Fullname
			}
		}

		// Get category name
		var categoryName string
		if article.CategoryId != 0 {
			category, err := _i.ArticleCategoriesRepo.FindOne(clientId, uint(article.CategoryId))
			if err == nil && category != nil {
				categoryName = category.Title
			}
		}

		response := &response.ArticleApprovalQueueResponse{
			ID:            article.ID,
			Title:         article.Title,
			Slug:          article.Slug,
			Description:   article.Description,
			CategoryName:  categoryName,
			AuthorName:    authorName,
			SubmittedAt:   flow.CreatedAt,
			CurrentStep:   flow.CurrentStep,
			TotalSteps:    len(workflowSteps),
			Priority:      priority,
			DaysInQueue:   daysInQueue,
			WorkflowName:  workflow.Name,
			CanApprove:    true,       // TODO: Implement based on user permissions
			EstimatedTime: "2-3 days", // TODO: Calculate based on historical data
		}

		responses = append(responses, response)
	}

	return responses, paging, nil
}

// GetArticlesWaitingForApproval gets articles that are waiting for approval by a specific user level
func (_i *articlesService) GetArticlesWaitingForApproval(clientId *uuid.UUID, authToken string, page, limit int) ([]*response.ArticleApprovalQueueResponse, paginator.Pagination, error) {
	// Extract user info from auth token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return nil, paginator.Pagination{}, errors.New("user not found from auth token")
	}

	// Use the existing repository method with proper filtering
	pagination := paginator.Pagination{
		Page:  page,
		Limit: limit,
	}
	req := request.ArticlesQueryRequest{
		Pagination: &pagination,
	}

	articles, paging, err := _i.Repo.GetAll(clientId, &user.UserLevelId, req)
	if err != nil {
		return nil, paging, err
	}

	// Build response
	var responses []*response.ArticleApprovalQueueResponse
	for _, article := range articles {
		response := &response.ArticleApprovalQueueResponse{
			ID:          article.ID,
			Title:       article.Title,
			Slug:        article.Slug,
			Description: article.Description,
			SubmittedAt: article.CreatedAt,
			CurrentStep: 1, // Will be updated with actual step
			CanApprove:  true,
		}
		responses = append(responses, response)
	}

	return responses, paging, nil
}

// CheckApprovalRequired checks if an article requires approval based on client settings
func (_i *articlesService) CheckApprovalRequired(clientId *uuid.UUID, articleId uint, userId uint, userLevelId uint) (bool, error) {
	// Get article to check category and other properties
	article, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return true, err // Default to requiring approval on error
	}

	// Check if article is already exempt
	if article.ApprovalExempt != nil && *article.ApprovalExempt {
		return false, nil
	}

	// Check if article should bypass approval
	if article.BypassApproval != nil && *article.BypassApproval {
		return false, nil
	}

	// Check client-level settings (this would require the client approval settings service)
	// For now, we'll use a simple check
	// TODO: Integrate with ClientApprovalSettingsService

	// Check if workflow is set to no approval
	if article.WorkflowId != nil {
		workflow, err := _i.ApprovalWorkflowsRepo.FindOne(clientId, *article.WorkflowId)
		if err == nil && workflow != nil {
			if workflow.RequiresApproval != nil && !*workflow.RequiresApproval {
				return false, nil
			}
			if workflow.AutoPublish != nil && *workflow.AutoPublish {
				return false, nil
			}
		}
	}

	// Default to requiring approval
	return true, nil
}

// AutoApproveArticle automatically approves an article (for no-approval scenarios)
func (_i *articlesService) AutoApproveArticle(clientId *uuid.UUID, articleId uint, reason string) error {
	article, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return err
	}

	// Update article status to approved
	updates := map[string]interface{}{
		"status_id":             2, // Assuming 2 = approved
		"is_publish":            true,
		"published_at":          time.Now(),
		"current_approval_step": 0, // Reset approval step
	}

	// Convert updates map to article entity
	articleUpdate := &entity.Articles{}
	if isPublish, ok := updates["is_publish"].(bool); ok {
		articleUpdate.IsPublish = &isPublish
	}
	if publishedAt, ok := updates["published_at"].(time.Time); ok {
		articleUpdate.PublishedAt = &publishedAt
	}
	if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
		articleUpdate.CurrentApprovalStep = &currentApprovalStep
	}

	err = _i.Repo.Update(clientId, articleId, articleUpdate)
	if err != nil {
		return err
	}

	// Create approval flow record for audit trail
	approvalFlow := &entity.ArticleApprovalFlows{
		ArticleId:     articleId,
		WorkflowId:    *article.WorkflowId,
		CurrentStep:   0,
		StatusId:      2, // approved
		SubmittedById: *article.CreatedById,
		SubmittedAt:   time.Now(),
		CompletedAt:   &[]time.Time{time.Now()}[0],
		ClientId:      clientId,
	}

	_, err = _i.ArticleApprovalFlowsRepo.Create(clientId, approvalFlow)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to create approval flow for auto-approved article")
		// Don't return error as article was already updated
	}

	_i.Log.Info().
		Str("article_id", fmt.Sprintf("%d", articleId)).
		Str("client_id", clientId.String()).
		Str("reason", reason).
		Msg("Article auto-approved")

	return nil
}

// GetClientApprovalSettings gets the approval settings for a client
func (_i *articlesService) GetClientApprovalSettings(clientId *uuid.UUID) (*response.ClientApprovalSettingsResponse, error) {
	// This would require the ClientApprovalSettingsService
	// For now, return default settings
	return &response.ClientApprovalSettingsResponse{
		ClientId:            clientId.String(),
		RequiresApproval:    true, // Default to requiring approval
		AutoPublishArticles: false,
		IsActive:            true,
	}, nil
}

// SetArticleApprovalExempt sets whether an article is exempt from approval
func (_i *articlesService) SetArticleApprovalExempt(clientId *uuid.UUID, articleId uint, exempt bool, reason string) error {
	updates := map[string]interface{}{
		"approval_exempt": &exempt,
	}

	if exempt {
		// If exempt, also set bypass approval
		bypass := true
		updates["bypass_approval"] = &bypass
		updates["current_approval_step"] = 0
	}

	// Convert updates map to article entity
	articleUpdate := &entity.Articles{}
	if approvalExempt, ok := updates["approval_exempt"].(*bool); ok {
		articleUpdate.ApprovalExempt = approvalExempt
	}
	if bypassApproval, ok := updates["bypass_approval"].(*bool); ok {
		articleUpdate.BypassApproval = bypassApproval
	}
	if currentApprovalStep, ok := updates["current_approval_step"].(int); ok {
		articleUpdate.CurrentApprovalStep = &currentApprovalStep
	}

	err := _i.Repo.Update(clientId, articleId, articleUpdate)
	if err != nil {
		return err
	}

	_i.Log.Info().
		Str("article_id", fmt.Sprintf("%d", articleId)).
		Str("client_id", clientId.String()).
		Bool("exempt", exempt).
		Str("reason", reason).
		Msg("Article approval exemption updated")

	return nil
}

// Publish publishes an article
func (_i *articlesService) Publish(clientId *uuid.UUID, articleId uint, authToken string) error {
	// Extract user info from auth token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found from auth token")
	}

	// Check if article exists
	article, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return err
	}

	// Check if article is already published
	if article.IsPublish != nil && *article.IsPublish {
		return errors.New("article is already published")
	}

	// Check if user has permission to publish
	// For now, we'll allow any authenticated user to publish
	// You can add more sophisticated permission checks here

	// Update article to published status
	isPublish := true
	publishedAt := time.Now()
	isDraftFalse := false
	statusIdTwo := 2 // Published status

	article.IsPublish = &isPublish
	article.PublishedAt = &publishedAt
	article.IsDraft = &isDraftFalse
	article.DraftedAt = nil
	article.StatusId = &statusIdTwo
	article.PublishSchedule = nil // Clear any scheduled publish time

	err = _i.Repo.Update(clientId, articleId, article)
	if err != nil {
		return err
	}

	// Create approval record for audit trail
	articleApproval := &entity.ArticleApprovals{
		ArticleId:       articleId,
		ApprovalBy:      user.ID,
		StatusId:        statusIdTwo,
		Message:         "Article published",
		ApprovalAtLevel: nil,
	}
	_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to create approval record for published article")
		// Don't return error as article was already updated
	}

	_i.Log.Info().
		Str("article_id", fmt.Sprintf("%d", articleId)).
		Str("client_id", clientId.String()).
		Str("user_id", fmt.Sprintf("%d", user.ID)).
		Msg("Article published successfully")

	return nil
}

// Unpublish unpublishes an article
func (_i *articlesService) Unpublish(clientId *uuid.UUID, articleId uint, authToken string) error {
	// Extract user info from auth token
	user := utilSvc.GetUserInfo(_i.Log, _i.UsersRepo, authToken)
	if user == nil {
		return errors.New("user not found from auth token")
	}

	// Check if article exists
	article, err := _i.Repo.FindOne(clientId, articleId)
	if err != nil {
		return err
	}

	// Check if article is already unpublished
	if article.IsPublish == nil || !*article.IsPublish {
		return errors.New("article is already unpublished")
	}

	// Check if user has permission to unpublish
	// For now, we'll allow any authenticated user to unpublish
	// You can add more sophisticated permission checks here

	// Update article to unpublished status
	isPublishFalse := false
	isDraftTrue := true
	draftedAt := time.Now()
	statusIdOne := 1 // Draft status

	article.IsPublish = &isPublishFalse
	article.PublishedAt = nil
	article.PublishSchedule = nil
	article.IsDraft = &isDraftTrue
	article.DraftedAt = &draftedAt
	article.StatusId = &statusIdOne

	err = _i.Repo.Update(clientId, articleId, article)
	if err != nil {
		return err
	}

	// Create approval record for audit trail
	articleApproval := &entity.ArticleApprovals{
		ArticleId:       articleId,
		ApprovalBy:      user.ID,
		StatusId:        statusIdOne,
		Message:         "Article unpublished",
		ApprovalAtLevel: nil,
	}
	_, err = _i.ArticleApprovalsRepo.Create(articleApproval)
	if err != nil {
		_i.Log.Error().Err(err).Msg("Failed to create approval record for unpublished article")
		// Don't return error as article was already updated
	}

	_i.Log.Info().
		Str("article_id", fmt.Sprintf("%d", articleId)).
		Str("client_id", clientId.String()).
		Str("user_id", fmt.Sprintf("%d", user.ID)).
		Msg("Article unpublished successfully")

	return nil
}

// findNextApprovalLevel finds the next higher level for approval
func (_i *articlesService) findNextApprovalLevel(clientId *uuid.UUID, currentLevelNumber int) int {
	// For now, we'll use a simple logic based on level numbers
	// Level 3 (POLRES) -> Level 2 (POLDAS) -> Level 1 (POLDAS)

	switch currentLevelNumber {
	case 3: // POLRES
		return 2 // Should be approved by POLDAS (Level 2)
	case 2: // POLDAS
		return 1 // Should be approved by Level 1
	case 1: // Highest level
		return 0 // No approval needed, can publish directly
	default:
		_i.Log.Warn().Int("currentLevel", currentLevelNumber).Msg("Unknown level, no approval needed")
		return 0
	}
}
