package service

import (
	"encoding/json"
	"errors"
	"strconv"
	"strings"
	"time"

	"web-qudo-be/app/database"
	"web-qudo-be/app/database/entity"
	"web-qudo-be/app/database/entity/users"
	aboutUsImageSvc "web-qudo-be/app/module/about_us_content_images/service"
	aboutUsSvc "web-qudo-be/app/module/about_us_contents/service"
	"web-qudo-be/app/module/cms_content_submissions/repository"
	"web-qudo-be/app/module/cms_content_submissions/request"
	"web-qudo-be/app/module/cms_content_submissions/response"
	heroImageSvc "web-qudo-be/app/module/hero_content_images/service"
	heroSvc "web-qudo-be/app/module/hero_contents/service"
	ourProductImageSvc "web-qudo-be/app/module/our_product_content_images/service"
	ourProductSvc "web-qudo-be/app/module/our_product_contents/service"
	ourServiceImageSvc "web-qudo-be/app/module/our_service_content_images/service"
	ourServiceSvc "web-qudo-be/app/module/our_service_contents/service"
	partnerSvc "web-qudo-be/app/module/partner_contents/service"
	popupImageReq "web-qudo-be/app/module/popup_news_content_images/request"
	popupImageSvc "web-qudo-be/app/module/popup_news_content_images/service"
	popupNewsReq "web-qudo-be/app/module/popup_news_contents/request"
	popupSvc "web-qudo-be/app/module/popup_news_contents/service"
	"web-qudo-be/utils/paginator"

	"github.com/google/uuid"
	"github.com/rs/zerolog"
)

const (
	cmsSubmissionPending  = "pending"
	cmsSubmissionApproved = "approved"
	cmsSubmissionRejected = "rejected"
	userLevelContributor  = uint(2)
	userLevelApprover     = uint(3)
)

type CmsContentSubmissionsService interface {
	Submit(clientID *uuid.UUID, user *users.Users, req *request.SubmitCmsContentSubmissionRequest) (*entity.CmsContentSubmission, error)
	List(clientID *uuid.UUID, user *users.Users, status string, mineOnly bool, p *paginator.Pagination) ([]response.CmsContentSubmissionListItem, *paginator.Pagination, error)
	Approve(clientID *uuid.UUID, user *users.Users, id uuid.UUID) error
	Reject(clientID *uuid.UUID, user *users.Users, id uuid.UUID, note string) error
}

type cmsContentSubmissionsService struct {
	Repo          repository.CmsContentSubmissionsRepository
	DB            *database.Database
	Hero          heroSvc.HeroContentsService
	HeroImg       heroImageSvc.HeroContentImagesService
	About         aboutUsSvc.AboutUsContentService
	AboutImg      aboutUsImageSvc.AboutUsContentImageService
	OurProduct    ourProductSvc.OurProductContentService
	OurProductImg ourProductImageSvc.OurProductContentImagesService
	OurService    ourServiceSvc.OurServiceContentService
	OurServiceImg ourServiceImageSvc.OurServiceContentImagesService
	Partner       partnerSvc.PartnerContentService
	Popup         popupSvc.PopupNewsContentsService
	PopupImg      popupImageSvc.PopupNewsContentImagesService
	Log           zerolog.Logger
}

func NewCmsContentSubmissionsService(
	repo repository.CmsContentSubmissionsRepository,
	db *database.Database,
	hero heroSvc.HeroContentsService,
	heroImg heroImageSvc.HeroContentImagesService,
	about aboutUsSvc.AboutUsContentService,
	aboutImg aboutUsImageSvc.AboutUsContentImageService,
	ourProduct ourProductSvc.OurProductContentService,
	ourProductImg ourProductImageSvc.OurProductContentImagesService,
	ourService ourServiceSvc.OurServiceContentService,
	ourServiceImg ourServiceImageSvc.OurServiceContentImagesService,
	partner partnerSvc.PartnerContentService,
	popup popupSvc.PopupNewsContentsService,
	popupImg popupImageSvc.PopupNewsContentImagesService,
	log zerolog.Logger,
) CmsContentSubmissionsService {
	return &cmsContentSubmissionsService{
		Repo:          repo,
		DB:            db,
		Hero:          hero,
		HeroImg:       heroImg,
		About:         about,
		AboutImg:      aboutImg,
		OurProduct:    ourProduct,
		OurProductImg: ourProductImg,
		OurService:    ourService,
		OurServiceImg: ourServiceImg,
		Partner:       partner,
		Popup:         popup,
		PopupImg:      popupImg,
		Log:           log,
	}
}

func (_i *cmsContentSubmissionsService) Submit(clientID *uuid.UUID, user *users.Users, req *request.SubmitCmsContentSubmissionRequest) (*entity.CmsContentSubmission, error) {
	if clientID == nil || user == nil {
		return nil, errors.New("unauthorized")
	}
	if user.UserLevelId != userLevelContributor {
		return nil, errors.New("only contributor (user level 2) can submit CMS drafts")
	}
	domain := strings.TrimSpace(strings.ToLower(req.Domain))
	if domain == "" {
		return nil, errors.New("domain is required")
	}
	title := strings.TrimSpace(req.Title)
	if title == "" {
		return nil, errors.New("title is required")
	}
	if len(req.Payload) == 0 || string(req.Payload) == "null" {
		return nil, errors.New("payload is required")
	}
	row := &entity.CmsContentSubmission{
		ID:            uuid.New(),
		ClientID:      *clientID,
		Domain:        domain,
		Title:         title,
		Status:        cmsSubmissionPending,
		Payload:       string(req.Payload),
		SubmittedByID: user.ID,
		CreatedAt:     time.Now(),
		UpdatedAt:     time.Now(),
	}
	if err := _i.Repo.Create(row); err != nil {
		return nil, err
	}
	return row, nil
}

func (_i *cmsContentSubmissionsService) List(clientID *uuid.UUID, user *users.Users, status string, mineOnly bool, p *paginator.Pagination) ([]response.CmsContentSubmissionListItem, *paginator.Pagination, error) {
	if clientID == nil || user == nil {
		return nil, p, errors.New("unauthorized")
	}
	st := strings.TrimSpace(strings.ToLower(status))
	var submittedBy *uint
	if mineOnly {
		submittedBy = &user.ID
	} else if user.UserLevelId == userLevelContributor {
		submittedBy = &user.ID
	}
	statusArg := status
	if st == "" {
		statusArg = "all"
	}
	rows, paging, err := _i.Repo.List(*clientID, statusArg, submittedBy, p)
	if err != nil {
		return nil, paging, err
	}
	out := make([]response.CmsContentSubmissionListItem, 0, len(rows))
	for _, row := range rows {
		name := ""
		var u users.Users
		if err := _i.DB.DB.Select("fullname").Where("id = ?", row.SubmittedByID).First(&u).Error; err == nil {
			name = u.Fullname
		}
		out = append(out, response.CmsContentSubmissionListItem{
			ID:            row.ID,
			Domain:        row.Domain,
			Title:         row.Title,
			Status:        row.Status,
			Payload:       row.Payload,
			SubmittedByID: row.SubmittedByID,
			SubmitterName: name,
			CreatedAt:     row.CreatedAt,
			UpdatedAt:     row.UpdatedAt,
		})
	}
	return out, paging, nil
}

func (_i *cmsContentSubmissionsService) Approve(clientID *uuid.UUID, user *users.Users, id uuid.UUID) error {
	if clientID == nil || user == nil {
		return errors.New("unauthorized")
	}
	if user.UserLevelId != userLevelApprover {
		return errors.New("only approver (user level 3) can approve CMS submissions")
	}
	row, err := _i.Repo.FindByID(*clientID, id)
	if err != nil {
		return err
	}
	if row.Status != cmsSubmissionPending {
		return errors.New("submission is not pending")
	}
	if err := _i.applyDomainPayload(row.Domain, row.Payload); err != nil {
		return err
	}
	now := time.Now()
	row.Status = cmsSubmissionApproved
	row.ReviewedByID = &user.ID
	row.ReviewNote = ""
	row.UpdatedAt = now
	return _i.Repo.Update(row)
}

func (_i *cmsContentSubmissionsService) Reject(clientID *uuid.UUID, user *users.Users, id uuid.UUID, note string) error {
	if clientID == nil || user == nil {
		return errors.New("unauthorized")
	}
	if user.UserLevelId != userLevelApprover {
		return errors.New("only approver (user level 3) can reject CMS submissions")
	}
	row, err := _i.Repo.FindByID(*clientID, id)
	if err != nil {
		return err
	}
	if row.Status != cmsSubmissionPending {
		return errors.New("submission is not pending")
	}
	now := time.Now()
	row.Status = cmsSubmissionRejected
	row.ReviewedByID = &user.ID
	row.ReviewNote = strings.TrimSpace(note)
	row.UpdatedAt = now
	return _i.Repo.Update(row)
}

func (_i *cmsContentSubmissionsService) applyDomainPayload(domain string, payloadJSON string) error {
	switch strings.ToLower(strings.TrimSpace(domain)) {
	case "hero":
		return _i.mergeHero([]byte(payloadJSON))
	case "about":
		return _i.mergeAbout([]byte(payloadJSON))
	case "product":
		return _i.mergeProduct([]byte(payloadJSON))
	case "service":
		return _i.mergeService([]byte(payloadJSON))
	case "partner":
		return _i.mergePartner([]byte(payloadJSON))
	case "popup":
		return _i.mergePopup([]byte(payloadJSON))
	default:
		return errors.New("unknown domain")
	}
}

type heroPayload struct {
	Action string `json:"action"`
	HeroID           string `json:"hero_id"`
	HeroImageID      string `json:"hero_image_id"`
	PrimaryTitle     string `json:"primary_title"`
	SecondaryTitle   string `json:"secondary_title"`
	Description      string `json:"description"`
	PrimaryCta       string `json:"primary_cta"`
	SecondaryCtaText string `json:"secondary_cta_text"`
	ImageURL         string `json:"image_url"`
}

func (_i *cmsContentSubmissionsService) mergeHero(raw []byte) error {
	var p heroPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		return errors.New("hero delete is not supported")
	}
	ent := &entity.HeroContents{
		PrimaryTitle:     p.PrimaryTitle,
		SecondaryTitle:   p.SecondaryTitle,
		Description:      p.Description,
		PrimaryCta:       p.PrimaryCta,
		SecondaryCtaText: p.SecondaryCtaText,
	}
	var heroUUID uuid.UUID
	if strings.TrimSpace(p.HeroID) == "" {
		saved, err := _i.Hero.Save(ent)
		if err != nil {
			return err
		}
		heroUUID = saved.ID
	} else {
		id, err := uuid.Parse(p.HeroID)
		if err != nil {
			return err
		}
		heroUUID = id
		if err := _i.Hero.Update(heroUUID, ent); err != nil {
			return err
		}
	}
	imgURL := strings.TrimSpace(p.ImageURL)
	if imgURL == "" {
		return nil
	}
	if strings.TrimSpace(p.HeroImageID) != "" {
		imgID, err := uuid.Parse(p.HeroImageID)
		if err != nil {
			return err
		}
		return _i.HeroImg.Update(imgID, &entity.HeroContentImages{
			ID:            imgID,
			HeroContentID: heroUUID,
			ImageURL:      imgURL,
		})
	}
	_, err := _i.HeroImg.Save(&entity.HeroContentImages{
		HeroContentID: heroUUID,
		ImageURL:      imgURL,
	})
	return err
}

type aboutPayload struct {
	Action            string `json:"action"`
	AboutID           *int   `json:"about_id"`
	AboutMediaImageID *int   `json:"about_media_image_id"`
	PrimaryTitle      string `json:"primary_title"`
	SecondaryTitle    string `json:"secondary_title"`
	Description       string `json:"description"`
	PrimaryCta        string `json:"primary_cta"`
	SecondaryCtaText  string `json:"secondary_cta_text"`
	MediaURL          string `json:"media_url"`
}

func (_i *cmsContentSubmissionsService) mergeAbout(raw []byte) error {
	var p aboutPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		return errors.New("about delete is not supported")
	}
	ent := &entity.AboutUsContent{
		PrimaryTitle:     p.PrimaryTitle,
		SecondaryTitle:   p.SecondaryTitle,
		Description:      p.Description,
		PrimaryCta:       p.PrimaryCta,
		SecondaryCtaText: p.SecondaryCtaText,
	}
	var aboutID uint
	if p.AboutID == nil || *p.AboutID == 0 {
		saved, err := _i.About.Save(ent)
		if err != nil {
			return err
		}
		aboutID = saved.ID
	} else {
		aboutID = uint(*p.AboutID)
		if err := _i.About.Update(aboutID, ent); err != nil {
			return err
		}
	}
	mediaURL := strings.TrimSpace(p.MediaURL)
	if mediaURL == "" {
		return nil
	}
	if p.AboutMediaImageID != nil && *p.AboutMediaImageID > 0 {
		_ = _i.AboutImg.Delete(uint(*p.AboutMediaImageID))
	}
	_, err := _i.AboutImg.SaveRemoteURL(aboutID, mediaURL, "")
	return err
}

type productPayload struct {
	Action string `json:"action"`
	ProductID      string `json:"product_id"`
	ProductImageID string `json:"product_image_id"`
	PrimaryTitle   string `json:"primary_title"`
	SecondaryTitle string `json:"secondary_title"`
	Description    string `json:"description"`
	LinkURL        string `json:"link_url"`
	ImageURL       string `json:"image_url"`
}

func (_i *cmsContentSubmissionsService) mergeProduct(raw []byte) error {
	var p productPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		if strings.TrimSpace(p.ProductID) == "" {
			return errors.New("product_id required for delete")
		}
		id, err := uuid.Parse(p.ProductID)
		if err != nil {
			return err
		}
		return _i.OurProduct.Delete(id)
	}
	ent := &entity.OurProductContent{
		PrimaryTitle:   p.PrimaryTitle,
		SecondaryTitle: p.SecondaryTitle,
		Description:    p.Description,
		LinkURL:        p.LinkURL,
	}
	var pid uuid.UUID
	if strings.TrimSpace(p.ProductID) == "" {
		saved, err := _i.OurProduct.Save(ent)
		if err != nil {
			return err
		}
		pid = saved.ID
	} else {
		id, err := uuid.Parse(p.ProductID)
		if err != nil {
			return err
		}
		pid = id
		if err := _i.OurProduct.Update(pid, ent); err != nil {
			return err
		}
	}
	imgURL := strings.TrimSpace(p.ImageURL)
	if imgURL == "" {
		return nil
	}
	if strings.TrimSpace(p.ProductImageID) != "" {
		imgID, err := uuid.Parse(p.ProductImageID)
		if err != nil {
			return err
		}
		return _i.OurProductImg.Update(imgID, &entity.OurProductContentImage{
			ID:                  imgID,
			OurProductContentID: pid,
			ImageURL:            imgURL,
		})
	}
	_, err := _i.OurProductImg.Save(&entity.OurProductContentImage{
		OurProductContentID: pid,
		ImageURL:            imgURL,
	})
	return err
}

type servicePayload struct {
	Action string `json:"action"`
	ServiceID        *int   `json:"service_id"`
	ServiceImageID   string `json:"service_image_id"`
	PrimaryTitle   string `json:"primary_title"`
	SecondaryTitle string `json:"secondary_title"`
	Description    string `json:"description"`
	LinkURL        string `json:"link_url"`
	ImageURL       string `json:"image_url"`
}

func (_i *cmsContentSubmissionsService) mergeService(raw []byte) error {
	var p servicePayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		if p.ServiceID == nil || *p.ServiceID == 0 {
			return errors.New("service_id required for delete")
		}
		return _i.OurService.Delete(uint(*p.ServiceID))
	}
	ent := &entity.OurServiceContent{
		PrimaryTitle:   p.PrimaryTitle,
		SecondaryTitle: p.SecondaryTitle,
		Description:    p.Description,
		LinkURL:        p.LinkURL,
	}
	var sid uint
	if p.ServiceID == nil || *p.ServiceID == 0 {
		saved, err := _i.OurService.Save(ent)
		if err != nil {
			return err
		}
		sid = saved.ID
	} else {
		sid = uint(*p.ServiceID)
		if err := _i.OurService.Update(sid, ent); err != nil {
			return err
		}
	}
	imgURL := strings.TrimSpace(p.ImageURL)
	if imgURL == "" {
		return nil
	}
	return _i.mergeServiceImage(sid, strings.TrimSpace(p.ServiceImageID), imgURL)
}

func (_i *cmsContentSubmissionsService) mergeServiceImage(sid uint, serviceImageID string, imgURL string) error {
	if serviceImageID != "" {
		n, err := strconv.ParseUint(serviceImageID, 10, 64)
		if err != nil {
			return err
		}
		imgID := uint(n)
		return _i.OurServiceImg.Update(imgID, &entity.OurServiceContentImage{
			ID:                  imgID,
			OurServiceContentID: sid,
			ImageURL:            imgURL,
		})
	}
	_, err := _i.OurServiceImg.Save(&entity.OurServiceContentImage{
		OurServiceContentID: sid,
		ImageURL:            imgURL,
	})
	return err
}

type partnerPayload struct {
	Action        string `json:"action"`
	PartnerID     string `json:"partner_id"`
	PrimaryTitle  string `json:"primary_title"`
	ImagePath     string `json:"image_path"`
	ImageURL      string `json:"image_url"`
}

func (_i *cmsContentSubmissionsService) mergePartner(raw []byte) error {
	var p partnerPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		if strings.TrimSpace(p.PartnerID) == "" {
			return errors.New("partner_id required for delete")
		}
		id, err := uuid.Parse(p.PartnerID)
		if err != nil {
			return err
		}
		return _i.Partner.Delete(id)
	}
	ent := &entity.PartnerContent{
		PrimaryTitle: strings.TrimSpace(p.PrimaryTitle),
		ImagePath:    p.ImagePath,
		ImageURL:     strings.TrimSpace(p.ImageURL),
	}
	if strings.TrimSpace(p.PartnerID) == "" {
		_, err := _i.Partner.Save(ent)
		return err
	}
	id, err := uuid.Parse(p.PartnerID)
	if err != nil {
		return err
	}
	return _i.Partner.Update(id, ent)
}

type popupPayload struct {
	Action           string `json:"action"`
	PopupID          *uint  `json:"popup_id"`
	PrimaryTitle     string `json:"primary_title"`
	SecondaryTitle   string `json:"secondary_title"`
	Description      string `json:"description"`
	PrimaryCta       string `json:"primary_cta"`
	SecondaryCtaText string `json:"secondary_cta_text"`
	MediaURL         string `json:"media_url"`
}

func (_i *cmsContentSubmissionsService) mergePopup(raw []byte) error {
	var p popupPayload
	if err := json.Unmarshal(raw, &p); err != nil {
		return err
	}
	if strings.EqualFold(p.Action, "delete") {
		if p.PopupID == nil || *p.PopupID == 0 {
			return errors.New("popup_id required for delete")
		}
		return _i.Popup.Delete(*p.PopupID)
	}
	if p.PopupID == nil || *p.PopupID == 0 {
		res, err := _i.Popup.Save(popupNewsReq.PopupNewsContentsCreateRequest{
			PrimaryTitle:     p.PrimaryTitle,
			SecondaryTitle:   p.SecondaryTitle,
			Description:      p.Description,
			PrimaryCTA:       p.PrimaryCta,
			SecondaryCTAText: p.SecondaryCtaText,
		})
		if err != nil {
			return err
		}
		return _i.attachPopupImage(res.ID, p.MediaURL)
	}
	pid := *p.PopupID
	if err := _i.Popup.Update(pid, popupNewsReq.PopupNewsContentsUpdateRequest{
		ID:               pid,
		PrimaryTitle:     p.PrimaryTitle,
		SecondaryTitle:   p.SecondaryTitle,
		Description:      p.Description,
		PrimaryCTA:       p.PrimaryCta,
		SecondaryCTAText: p.SecondaryCtaText,
	}); err != nil {
		return err
	}
	return _i.attachPopupImage(pid, p.MediaURL)
}

func (_i *cmsContentSubmissionsService) attachPopupImage(popupID uint, mediaURL string) error {
	mediaURL = strings.TrimSpace(mediaURL)
	if mediaURL == "" {
		return nil
	}
	return _i.PopupImg.Save(popupImageReq.PopupNewsContentImagesCreateRequest{
		PopupNewsContentID: popupID,
		MediaPath:          "",
		MediaURL:           mediaURL,
	})
}
