package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"

	"github.com/PuerkitoBio/goquery"
	fhttp "github.com/bogdanfinn/fhttp"
	tls_client "github.com/bogdanfinn/tls-client"
	"github.com/bogdanfinn/tls-client/profiles"
	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
	"github.com/tidwall/gjson"
)

// ==========================================
// Struct Data
// ==========================================

type ReviewFeed struct {
	ReviewerName     string `json:"reviewer_name"`
	ReviewerLink     string `json:"reviewer_link"`
	ReviewerProfile  string `json:"reviewer_profile"`
	ReviewerSkinDesc string `json:"reviewer_skin_desc"`
	ProductName      string `json:"product_name"`
	ProductLink      string `json:"product_link"`
	ProductEndpoint  string `json:"product_endpoint"`
	ProductImage     string `json:"product_image"`
	BrandName        string `json:"brand_name"`
	BrandLink        string `json:"brand_link"`
	BrandEndpoint    string `json:"brand_endpoint"`
	ProductRating    string `json:"product_rating"`
	ProductRevCount  string `json:"product_review_count"`
	ReviewDate       string `json:"review_date"`
	Recommendation   string `json:"recommendation"`
	ReviewText       string `json:"review_text"`
	UsagePeriod      string `json:"usage_period"`
	PurchasePoint    string `json:"purchase_point"`
}

type PaginationInfo struct {
	CurrentPage int64 `json:"current_page"`
	TotalItems  int64 `json:"total_items"`
	TotalPage   int64 `json:"total_page"`
}

type BrandInfo struct {
	Name        string         `json:"name"`
	Logo        string         `json:"logo"`
	Description string         `json:"description"`
	TotalProduk int64          `json:"total_products"`
	Pagination  PaginationInfo `json:"pagination"`
	Products    []ProductBrief `json:"products"`
}

type ProductBrief struct {
	Name        string `json:"name"`
	Slug        string `json:"slug"`
	Image       string `json:"image"`
	Description string `json:"description"`
	Category    string `json:"category"`
	Rating      string `json:"rating"`
	TotalReview int64  `json:"total_review"`
	Price       int64  `json:"price"`
}

type CategoryInfo struct {
	CategoryName string         `json:"category_name"`
	Pagination   PaginationInfo `json:"pagination"`
	Products     []ProductBrief `json:"products"`
}

type ProductDetail struct {
	BrandName             string          `json:"brand_name"`
	ProductName           string          `json:"product_name"`
	Category              string          `json:"category"`
	CategoryLink          string          `json:"category_link"`
	CategoryEndpoint      string          `json:"category_endpoint"`
	Description           string          `json:"description"`
	Image                 string          `json:"image"`
	Price                 int64           `json:"price"`
	RatingAverage         string          `json:"rating_average"`
	RatingTotal           int64           `json:"rating_total"`
	RecommendedPercentage int64           `json:"recommended_percentage"`
	Photos                []string        `json:"photos"`
	ReviewsPagination     PaginationInfo  `json:"reviews_pagination"`
	Reviews               []ProductReview `json:"reviews"`
}

type ProductReview struct {
	ReviewerName   string `json:"reviewer_name"`
	ReviewerAge    string `json:"reviewer_age"`
	ReviewerSkin   string `json:"reviewer_skin"`
	ReviewText     string `json:"review_text"`
	RatingGiven    int64  `json:"rating_given"`
	Recommendation string `json:"recommendation"`
	ReviewDate     string `json:"review_date"`
	UsagePeriod    string `json:"usage_period"`
	PurchasePoint  string `json:"purchase_point"`
}

type BrandListItem struct {
	Name        string `json:"name"`
	Slug        string `json:"slug"`
	Logo        string `json:"logo"`
	Type        string `json:"type"`
	Description string `json:"description"`
}

type BrandListResponse struct {
	Pagination PaginationInfo  `json:"pagination"`
	Brands     []BrandListItem `json:"brands"`
}

// ==========================================
// Client Helper
// ==========================================

func createFreshClient() tls_client.HttpClient {
	options := []tls_client.HttpClientOption{
		tls_client.WithTimeoutSeconds(30),
		tls_client.WithClientProfile(profiles.Chrome_120),
		tls_client.WithNotFollowRedirects(),
		tls_client.WithCookieJar(tls_client.NewCookieJar()),
	}
	client, _ := tls_client.NewHttpClient(tls_client.NewNoopLogger(), options...)
	return client
}

func main() {
	r := gin.Default()
	r.Use(cors.Default())

	// 1. ENDPOINT FEED
	r.GET("/api/feed", func(c *gin.Context) {
		client := createFreshClient()
		targetURL := "https://reviews.femaledaily.com/"
		req, _ := fhttp.NewRequest(fhttp.MethodGet, targetURL, nil)
		req.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		resp, err := client.Do(req)

		if err != nil || resp.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengakses target URL Feed"})
			return
		}
		defer resp.Body.Close()

		doc, _ := goquery.NewDocumentFromReader(resp.Body)
		nextDataScript := doc.Find("script#__NEXT_DATA__").Text()

		// Fallback mencari __NEXT_DATA__ jika tidak ada di script id
		if nextDataScript == "" {
			doc.Find("script").Each(func(i int, s *goquery.Selection) {
				text := s.Text()
				if strings.Contains(text, "__NEXT_DATA__ =") {
					startIndex := strings.Index(text, "__NEXT_DATA__ = ") + len("__NEXT_DATA__ = ")
					jsonPart := text[startIndex:]
					endMarkers := []string{"module=", "__NEXT_LOADED_PAGES__"}
					for _, marker := range endMarkers {
						if idx := strings.Index(jsonPart, marker); idx != -1 {
							jsonPart = jsonPart[:idx]
						}
					}
					nextDataScript = strings.TrimSpace(jsonPart)
					nextDataScript = strings.TrimRight(nextDataScript, ";")
				}
			})
		}

		if nextDataScript == "" {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Data JSON tidak ditemukan di halaman Feed"})
			return
		}

		var feedResults []ReviewFeed
		baseURL := "https://reviews.femaledaily.com/"
		reviewsArray := gjson.Get(nextDataScript, "props.pageProps.initialState.storeReview.data").Array()

		for _, rev := range reviewsArray {
			reviewerName := rev.Get("user.username").String()
			productName := rev.Get("product.name").String()

			if reviewerName != "" && productName != "" {
				skinType := rev.Get("user.skin.type").String()
				skinTone := rev.Get("user.skin.tone").String()
				skinUndertone := rev.Get("user.skin.undertone").String()
				skinInfo := fmt.Sprintf("%s, %s, %s", skinType, skinTone, skinUndertone)
				skinInfo = strings.Trim(strings.ReplaceAll(skinInfo, ", , ", ", "), " ,")

				userLink := fmt.Sprintf("%saccount.femaledaily.com/user/fd/%s", baseURL, reviewerName)
				brandSlug := rev.Get("product.brand.slug").String()
				prodSlug := rev.Get("product.slug").String()
				catParentSlug := rev.Get("product.category.parent.slug").String()
				catChildSlug := rev.Get("product.category.slug").String()

				prodLink := fmt.Sprintf("%sproducts/%s/%s/%s/%s", baseURL, catParentSlug, catChildSlug, brandSlug, prodSlug)
				brandLink := fmt.Sprintf("%sbrands/product/%s", baseURL, brandSlug)

				productEndpointPath := fmt.Sprintf("products/%s/%s/%s/%s", catParentSlug, catChildSlug, brandSlug, prodSlug)
				brandEndpointPath := fmt.Sprintf("brands/product/%s", brandSlug)
				ratingVal := rev.Get("product.rating.average").Float()
				ratingStr := fmt.Sprintf("%.1f", ratingVal)

				feedResults = append(feedResults, ReviewFeed{
					ReviewerName:     reviewerName,
					ReviewerLink:     userLink,
					ReviewerProfile:  rev.Get("user.beauty_level").String(),
					ReviewerSkinDesc: skinInfo,
					ProductName:      productName,
					ProductLink:      prodLink,
					ProductEndpoint:  fmt.Sprintf("/api/product?target=%s", url.QueryEscape(productEndpointPath)),
					ProductImage:     rev.Get("product.image").String(),
					BrandName:        rev.Get("product.brand.name").String(),
					BrandLink:        brandLink,
					BrandEndpoint:    fmt.Sprintf("/api/brand?target=%s", url.QueryEscape(brandEndpointPath)),
					ProductRating:    ratingStr,
					ProductRevCount:  rev.Get("product.rating.total").String(),
					ReviewDate:       rev.Get("date").String(),
					Recommendation:   rev.Get("is_recommended").String(),
					ReviewText:       rev.Get("text").String(),
					UsagePeriod:      rev.Get("duration_of_use").String(),
					PurchasePoint:    rev.Get("place_to_get.place").String(),
				})
			}
		}
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "total": len(feedResults), "data": feedResults})
	})

	// 2. ENDPOINT BRAND
	r.GET("/api/brand", func(c *gin.Context) {
		client := createFreshClient()
		targetPath := c.Query("target")
		if targetPath == "" {
			c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "Parameter 'target' wajib diisi"})
			return
		}
		pageParam := c.DefaultQuery("page", "1")

		targetPath = strings.TrimPrefix(targetPath, "https://reviews.femaledaily.com/")
		targetPath = strings.TrimPrefix(targetPath, "/")
		if !strings.HasPrefix(targetPath, "brands/") {
			targetPath = "brands/product/" + targetPath
		}

		parts := strings.Split(targetPath, "/")
		if len(parts) < 3 {
			c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "Format target tidak valid"})
			return
		}
		brandSlug := parts[2]

		htmlURL := fmt.Sprintf("https://reviews.femaledaily.com/%s", targetPath)
		reqHTML, _ := fhttp.NewRequest(fhttp.MethodGet, htmlURL, nil)
		reqHTML.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		respHTML, _ := client.Do(reqHTML)
		cookies := respHTML.Cookies()
		doc, _ := goquery.NewDocumentFromReader(respHTML.Body)
		respHTML.Body.Close()

		htmlContent, _ := doc.Html()
		dynamicAPIKey := "client03-TSbs94s3q5H9PP2yNPBr"
		re := regexp.MustCompile(`client\d{2}-[A-Za-z0-9]+(-[A-Za-z0-9]+)*`)
		if foundKey := re.FindString(htmlContent); foundKey != "" {
			dynamicAPIKey = foundKey
		}

		nextDataScript := doc.Find("script#__NEXT_DATA__").Text()
		var brandInfo BrandInfo
		brandDataPath := "props.pageProps.dehydratedState.queries.0.state.data.data"
		brandInfo.Name = gjson.Get(nextDataScript, brandDataPath+".name").String()
		brandInfo.Logo = gjson.Get(nextDataScript, brandDataPath+".logo").String()
		brandInfo.Description = gjson.Get(nextDataScript, brandDataPath+".description").String()
		brandInfo.TotalProduk = gjson.Get(nextDataScript, brandDataPath+".total_variant").Int()

		var productsArray []gjson.Result
		if pageParam == "1" {
			paginationPath := "props.pageProps.initialBrandProducts.pagination"
			brandInfo.Pagination = PaginationInfo{
				CurrentPage: gjson.Get(nextDataScript, paginationPath+".page").Int(),
				TotalItems:  gjson.Get(nextDataScript, paginationPath+".total").Int(),
				TotalPage:   gjson.Get(nextDataScript, paginationPath+".total_page").Int(),
			}
			productsArray = gjson.Get(nextDataScript, "props.pageProps.initialBrandProducts.data").Array()
		} else {
			apiURL := fmt.Sprintf("https://reviews.femaledaily.com/service_review/api/products?brand=%s&page=%s&limit=16", brandSlug, pageParam)
			reqAPI, _ := fhttp.NewRequest(fhttp.MethodGet, apiURL, nil)
			reqAPI.Header.Set("key", dynamicAPIKey)
			reqAPI.Header.Set("device", "3")
			reqAPI.Header.Set("version", "1.5")
			reqAPI.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
			reqAPI.Header.Set("Accept", "application/json")
			reqAPI.Header.Set("Referer", htmlURL)
			for _, cookie := range cookies {
				reqAPI.AddCookie(cookie)
			}
			respAPI, _ := client.Do(reqAPI)
			defer respAPI.Body.Close()
			bodyBytes, _ := io.ReadAll(respAPI.Body)
			apiJSON := string(bodyBytes)
			paginationData := gjson.Get(apiJSON, "payload.pagination")
			if !paginationData.Exists() {
				paginationData = gjson.Get(apiJSON, "pagination")
			}
			brandInfo.Pagination = PaginationInfo{
				CurrentPage: paginationData.Get("page").Int(),
				TotalItems:  paginationData.Get("total").Int(),
				TotalPage:   paginationData.Get("total_page").Int(),
			}
			productsData := gjson.Get(apiJSON, "payload.data")
			if !productsData.Exists() {
				productsData = gjson.Get(apiJSON, "data")
			}
			productsArray = productsData.Array()
		}

		for _, prod := range productsArray {
			catName := prod.Get("categories.0.parent.name").String()
			if catName == "" {
				catName = prod.Get("categories.0.name").String()
			}
			ratingStr := fmt.Sprintf("%.1f", prod.Get("rating.rating_overall").Float())
			name := prod.Get("product.name").String()
			if name == "" {
				name = prod.Get("name").String()
			}
			brandInfo.Products = append(brandInfo.Products, ProductBrief{
				Name:        name,
				Slug:        prod.Get("slug").String(),
				Description: prod.Get("description").String(),
				Price:       prod.Get("price").Int(),
				TotalReview: prod.Get("total_review").Int(),
				Rating:      ratingStr,
				Category:    catName,
				Image:       prod.Get("assets.0.url").String(),
			})
		}
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "data": brandInfo})
	})

	// 3. ENDPOINT PRODUCT DETAIL
	r.GET("/api/product", func(c *gin.Context) {
		client := createFreshClient()
		targetPath := c.Query("target")
		if targetPath == "" {
			c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "Parameter 'target' wajib diisi"})
			return
		}
		pageParam := c.DefaultQuery("page", "1")
		targetPath = strings.TrimPrefix(targetPath, "https://reviews.femaledaily.com/")
		targetPath = strings.TrimPrefix(targetPath, "/")
		if !strings.HasPrefix(targetPath, "products/") {
			targetPath = "products/" + targetPath
		}
		htmlURL := fmt.Sprintf("https://reviews.femaledaily.com/%s", targetPath)
		reqHTML, _ := fhttp.NewRequest(fhttp.MethodGet, htmlURL, nil)
		reqHTML.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		respHTML, err := client.Do(reqHTML)
		if err != nil || respHTML.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengakses target URL produk"})
			return
		}
		cookies := respHTML.Cookies()
		defer respHTML.Body.Close()
		doc, _ := goquery.NewDocumentFromReader(respHTML.Body)
		htmlContent, _ := doc.Html()
		dynamicAPIKey := "client03-TSbs94s3q5H9PP2yNPBr"
		re := regexp.MustCompile(`client\d{2}-[A-Za-z0-9]+(-[A-Za-z0-9]+)*`)
		if foundKey := re.FindString(htmlContent); foundKey != "" {
			dynamicAPIKey = foundKey
		}
		var nextDataScript string
		doc.Find("script").Each(func(i int, s *goquery.Selection) {
			text := s.Text()
			if strings.Contains(text, "__NEXT_DATA__ =") {
				startIndex := strings.Index(text, "__NEXT_DATA__ = ") + len("__NEXT_DATA__ = ")
				jsonPart := text[startIndex:]
				endMarkers := []string{"module=", "__NEXT_LOADED_PAGES__"}
				for _, marker := range endMarkers {
					if idx := strings.Index(jsonPart, marker); idx != -1 {
						jsonPart = jsonPart[:idx]
					}
				}
				nextDataScript = strings.TrimSpace(jsonPart)
				nextDataScript = strings.TrimRight(nextDataScript, ";")
			}
		})
		if nextDataScript == "" {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Data __NEXT_DATA__ tidak ditemukan di halaman produk"})
			return
		}
		var prodDetail ProductDetail
		jsonPathPrefix := "props.initialState.storeProduct.data."
		productID := gjson.Get(nextDataScript, jsonPathPrefix+"id").String()
		prodDetail.BrandName = gjson.Get(nextDataScript, jsonPathPrefix+"brand.name").String()
		prodDetail.ProductName = gjson.Get(nextDataScript, jsonPathPrefix+"name").String()
		prodDetail.Description = gjson.Get(nextDataScript, jsonPathPrefix+"desc").String()
		prodDetail.Image = gjson.Get(nextDataScript, jsonPathPrefix+"image").String()
		prodDetail.Price = gjson.Get(nextDataScript, jsonPathPrefix+"price").Int()
		catName := gjson.Get(nextDataScript, jsonPathPrefix+"category.parent.name").String()
		subCat := gjson.Get(nextDataScript, jsonPathPrefix+"category.name").String()
		if catName != "" && subCat != "" {
			prodDetail.Category = catName + " - " + subCat
		} else {
			prodDetail.Category = catName + subCat
		}
		parentSlug := gjson.Get(nextDataScript, jsonPathPrefix+"category.parent.slug").String()
		childSlug := gjson.Get(nextDataScript, jsonPathPrefix+"category.slug").String()
		catPath := "products"
		if parentSlug != "" {
			catPath += "/" + parentSlug
		}
		if childSlug != "" {
			catPath += "/" + childSlug
		}
		if parentSlug != "" || childSlug != "" {
			prodDetail.CategoryLink = "https://reviews.femaledaily.com/" + catPath
			prodDetail.CategoryEndpoint = fmt.Sprintf("/api/category?target=%s", url.QueryEscape(catPath))
		}
		ratingVal := gjson.Get(nextDataScript, jsonPathPrefix+"rating.average").Float()
		prodDetail.RatingAverage = fmt.Sprintf("%.1f", ratingVal)
		prodDetail.RatingTotal = gjson.Get(nextDataScript, jsonPathPrefix+"rating.total").Int()
		prodDetail.RecommendedPercentage = gjson.Get(nextDataScript, jsonPathPrefix+"rating.recommend_percentage").Int()
		var reviewsArray []gjson.Result
		if pageParam == "1" {
			reviewsPath := "props.initialState.storeProduct.reviews"
			prodDetail.ReviewsPagination = PaginationInfo{
				CurrentPage: gjson.Get(nextDataScript, reviewsPath+".pagination.page").Int(),
				TotalItems:  gjson.Get(nextDataScript, reviewsPath+".pagination.total").Int(),
				TotalPage:   gjson.Get(nextDataScript, reviewsPath+".pagination.total_page").Int(),
			}
			reviewsArray = gjson.Get(nextDataScript, reviewsPath+".data").Array()
		} else {
			coreAPIUrl := fmt.Sprintf("https://api.femaledaily.com/app/v1/reviews?product_id=%s&age_range_ids=&skin_type_ids=&skin_tone_ids=&skin_undertone_ids=&hair_texture_ids=&hair_type_ids=&sort=newest&limit=10&page=%s", productID, pageParam)
			reqAPI, _ := fhttp.NewRequest(fhttp.MethodGet, coreAPIUrl, nil)
			reqAPI.Header.Set("key", dynamicAPIKey)
			reqAPI.Header.Set("device", "3")
			reqAPI.Header.Set("version", "1.5")
			reqAPI.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
			reqAPI.Header.Set("Accept", "application/json")
			reqAPI.Header.Set("Origin", "https://reviews.femaledaily.com")
			reqAPI.Header.Set("Referer", htmlURL)
			for _, cookie := range cookies {
				reqAPI.AddCookie(cookie)
			}
			respAPI, err := client.Do(reqAPI)
			if err != nil || respAPI.StatusCode != 200 {
				c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Gagal memuat Core API Ulasan. Status: %d", respAPI.StatusCode)})
				return
			}
			defer respAPI.Body.Close()
			bodyBytes, _ := io.ReadAll(respAPI.Body)
			apiJSON := string(bodyBytes)
			paginationData := gjson.Get(apiJSON, "payload.pagination")
			if !paginationData.Exists() {
				paginationData = gjson.Get(apiJSON, "pagination")
			}
			prodDetail.ReviewsPagination = PaginationInfo{
				CurrentPage: paginationData.Get("page").Int(),
				TotalItems:  paginationData.Get("total").Int(),
				TotalPage:   paginationData.Get("total_page").Int(),
			}
			reviewsData := gjson.Get(apiJSON, "payload.data")
			if !reviewsData.Exists() {
				reviewsData = gjson.Get(apiJSON, "data")
			}
			reviewsArray = reviewsData.Array()
		}
		for _, rev := range reviewsArray {
			skinType := rev.Get("user.skin.type").String()
			skinTone := rev.Get("user.skin.tone").String()
			skinUndertone := rev.Get("user.skin.undertone").String()
			skinInfo := fmt.Sprintf("%s, %s, %s", skinType, skinTone, skinUndertone)
			skinInfo = strings.Trim(strings.ReplaceAll(skinInfo, ", , ", ", "), " ,")
			itemReview := ProductReview{
				ReviewerName:   rev.Get("user.username").String(),
				ReviewerAge:    rev.Get("user.age_range").String(),
				ReviewerSkin:   skinInfo,
				ReviewText:     rev.Get("text").String(),
				Recommendation: rev.Get("is_recommended").String(),
				ReviewDate:     rev.Get("created_at").String(),
				UsagePeriod:    rev.Get("duration_of_use").String(),
				PurchasePoint:  rev.Get("place_to_get.place").String(),
			}
			if pageParam == "1" {
				itemReview.RatingGiven = rev.Get("rating.user").Int()
				itemReview.ReviewDate = rev.Get("date").String()
			} else {
				itemReview.RatingGiven = rev.Get("rating").Int()
			}
			photosArray := rev.Get("photos").Array()
			for _, pic := range photosArray {
				imgURL := pic.Get("img_url").String()
				if imgURL != "" {
					prodDetail.Photos = append(prodDetail.Photos, imgURL)
				}
			}
			prodDetail.Reviews = append(prodDetail.Reviews, itemReview)
		}
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "data": prodDetail})
	})

	// 4. ENDPOINT CATEGORY
	r.GET("/api/category", func(c *gin.Context) {
		client := createFreshClient()
		targetPath := c.Query("target")
		if targetPath == "" {
			c.IndentedJSON(http.StatusBadRequest, gin.H{"error": "Parameter 'target' wajib diisi"})
			return
		}
		pageParam := c.DefaultQuery("page", "1")
		targetPath = strings.TrimPrefix(targetPath, "https://reviews.femaledaily.com/")
		targetPath = strings.TrimPrefix(targetPath, "/")
		if !strings.HasPrefix(targetPath, "products/") {
			targetPath = "products/" + targetPath
		}
		parts := strings.Split(targetPath, "/")
		categorySlug := parts[len(parts)-1]
		htmlURL := fmt.Sprintf("https://reviews.femaledaily.com/%s", targetPath)
		reqHTML, _ := fhttp.NewRequest(fhttp.MethodGet, htmlURL, nil)
		reqHTML.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		respHTML, err := client.Do(reqHTML)
		if err != nil || respHTML.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengakses target URL kategori"})
			return
		}
		cookies := respHTML.Cookies()
		defer respHTML.Body.Close()
		doc, _ := goquery.NewDocumentFromReader(respHTML.Body)
		htmlContent, _ := doc.Html()
		dynamicAPIKey := "client03-TSbs94s3q5H9PP2yNPBr"
		re := regexp.MustCompile(`client\d{2}-[A-Za-z0-9]+(-[A-Za-z0-9]+)*`)
		if foundKey := re.FindString(htmlContent); foundKey != "" {
			dynamicAPIKey = foundKey
		}
		var nextDataScript string
		nextDataScript = doc.Find("script#__NEXT_DATA__").Text()
		if nextDataScript == "" {
			doc.Find("script").Each(func(i int, s *goquery.Selection) {
				text := s.Text()
				if strings.Contains(text, "__NEXT_DATA__ =") {
					startIndex := strings.Index(text, "__NEXT_DATA__ = ") + len("__NEXT_DATA__ = ")
					jsonPart := text[startIndex:]
					endMarkers := []string{"module=", "__NEXT_LOADED_PAGES__"}
					for _, marker := range endMarkers {
						if idx := strings.Index(jsonPart, marker); idx != -1 {
							jsonPart = jsonPart[:idx]
						}
					}
					nextDataScript = strings.TrimSpace(jsonPart)
					nextDataScript = strings.TrimRight(nextDataScript, ";")
				}
			})
		}
		if nextDataScript == "" {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Data __NEXT_DATA__ tidak ditemukan di halaman kategori"})
			return
		}
		var catInfo CategoryInfo
		catInfo.CategoryName = targetPath
		jsonPathPrefix := "props.pageProps.initialCategoryProducts."
		if !gjson.Get(nextDataScript, jsonPathPrefix+"data").Exists() {
			jsonPathPrefix = "props.pageProps.initialProducts."
		}
		if !gjson.Get(nextDataScript, jsonPathPrefix+"data").Exists() {
			jsonPathPrefix = "props.initialState.storeSearchProducts."
		}
		var productsArray []gjson.Result
		if pageParam == "1" {
			catInfo.Pagination = PaginationInfo{
				CurrentPage: gjson.Get(nextDataScript, jsonPathPrefix+"pagination.page").Int(),
				TotalItems:  gjson.Get(nextDataScript, jsonPathPrefix+"pagination.total").Int(),
				TotalPage:   gjson.Get(nextDataScript, jsonPathPrefix+"pagination.total_page").Int(),
			}
			productsArray = gjson.Get(nextDataScript, jsonPathPrefix+"data").Array()
		} else {
			apiURL := fmt.Sprintf("https://reviews.femaledaily.com/service_review/api/products?category=%s&sort=popular&page=%s&limit=20&type=all", categorySlug, pageParam)
			reqAPI, _ := fhttp.NewRequest(fhttp.MethodGet, apiURL, nil)
			reqAPI.Header.Set("key", dynamicAPIKey)
			reqAPI.Header.Set("device", "3")
			reqAPI.Header.Set("version", "1.5")
			reqAPI.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
			reqAPI.Header.Set("Accept", "application/json, text/plain, */*")
			reqAPI.Header.Set("Origin", "https://reviews.femaledaily.com")
			reqAPI.Header.Set("Referer", htmlURL)
			for _, cookie := range cookies {
				reqAPI.AddCookie(cookie)
			}
			respAPI, err := client.Do(reqAPI)
			if err != nil || respAPI.StatusCode != 200 {
				c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Gagal memuat API Kategori Paginasi. Status: %d", respAPI.StatusCode)})
				return
			}
			defer respAPI.Body.Close()
			bodyBytes, _ := io.ReadAll(respAPI.Body)
			apiJSON := string(bodyBytes)
			paginationData := gjson.Get(apiJSON, "payload.pagination")
			if !paginationData.Exists() {
				paginationData = gjson.Get(apiJSON, "pagination")
			}
			catInfo.Pagination = PaginationInfo{
				CurrentPage: paginationData.Get("page").Int(),
				TotalItems:  paginationData.Get("total").Int(),
				TotalPage:   paginationData.Get("total_page").Int(),
			}
			productsData := gjson.Get(apiJSON, "payload.data")
			if !productsData.Exists() {
				productsData = gjson.Get(apiJSON, "data")
			}
			productsArray = productsData.Array()
		}
		for _, prod := range productsArray {
			catName := prod.Get("categories.0.parent.name").String()
			if catName == "" {
				catName = prod.Get("categories.0.name").String()
			}
			ratingVal := prod.Get("rating.rating_overall").Float()
			ratingStr := fmt.Sprintf("%.1f", ratingVal)
			name := prod.Get("product.name").String()
			if name == "" {
				name = prod.Get("name").String()
			}
			catInfo.Products = append(catInfo.Products, ProductBrief{
				Name:        name,
				Slug:        prod.Get("slug").String(),
				Description: prod.Get("description").String(),
				Price:       prod.Get("price").Int(),
				TotalReview: prod.Get("total_review").Int(),
				Rating:      ratingStr,
				Category:    catName,
				Image:       prod.Get("assets.0.url").String(),
			})
		}
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "data": catInfo})
	})

	// 5. ENDPOINT CATEGORY LIST
	r.GET("/api/category-list", func(c *gin.Context) {
		client := createFreshClient()
		htmlURL := "https://reviews.femaledaily.com/"
		reqHTML, _ := fhttp.NewRequest(fhttp.MethodGet, htmlURL, nil)
		reqHTML.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		respHTML, err := client.Do(reqHTML)
		if err != nil || respHTML.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengakses halaman utama"})
			return
		}
		cookies := respHTML.Cookies()
		defer respHTML.Body.Close()
		doc, _ := goquery.NewDocumentFromReader(respHTML.Body)
		htmlContent, _ := doc.Html()
		dynamicAPIKey := "client03-TSbs94s3q5H9PP2yNPBr"
		re := regexp.MustCompile(`client\d{2}-[A-Za-z0-9]+(-[A-Za-z0-9]+)*`)
		if foundKey := re.FindString(htmlContent); foundKey != "" {
			dynamicAPIKey = foundKey
		}
		apiURL := "https://reviews.femaledaily.com/api-new/category"
		reqAPI, _ := fhttp.NewRequest(fhttp.MethodGet, apiURL, nil)
		reqAPI.Header.Set("key", dynamicAPIKey)
		reqAPI.Header.Set("device", "3")
		reqAPI.Header.Set("version", "1.5")
		reqAPI.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		reqAPI.Header.Set("Accept", "application/json")
		reqAPI.Header.Set("Origin", "https://reviews.femaledaily.com")
		reqAPI.Header.Set("Referer", htmlURL)
		for _, cookie := range cookies {
			reqAPI.AddCookie(cookie)
		}
		respAPI, err := client.Do(reqAPI)
		if err != nil || respAPI.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Gagal mengambil daftar kategori. Status: %d", respAPI.StatusCode)})
			return
		}
		defer respAPI.Body.Close()
		bodyBytes, _ := io.ReadAll(respAPI.Body)
		categoryData := gjson.Get(string(bodyBytes), "data").Value()
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "data": categoryData})
	})

	// 6. ENDPOINT BRAND LIST A-Z
	r.GET("/api/brand-list", func(c *gin.Context) {
		client := createFreshClient()
		alphabet := c.DefaultQuery("alphabet", "A")
		pageParam := c.DefaultQuery("page", "1")
		htmlURL := fmt.Sprintf("https://reviews.femaledaily.com/brands?type=product&alphabet=%s&origin=&sort=name", url.QueryEscape(alphabet))
		reqHTML, _ := fhttp.NewRequest(fhttp.MethodGet, htmlURL, nil)
		reqHTML.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
		respHTML, err := client.Do(reqHTML)
		if err != nil || respHTML.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengakses halaman list brand"})
			return
		}
		cookies := respHTML.Cookies()
		doc, _ := goquery.NewDocumentFromReader(respHTML.Body)
		respHTML.Body.Close()
		htmlContent, _ := doc.Html()
		dynamicAPIKey := "client03-TSbs94s3q5H9PP2yNPBr"
		re := regexp.MustCompile(`client\d{2}-[A-Za-z0-9]+(-[A-Za-z0-9]+)*`)
		if foundKey := re.FindString(htmlContent); foundKey != "" {
			dynamicAPIKey = foundKey
		}
		doRequest := func(urlToFetch string) (*fhttp.Response, error) {
			reqAPI, _ := fhttp.NewRequest(fhttp.MethodGet, urlToFetch, nil)
			reqAPI.Header.Set("key", dynamicAPIKey)
			reqAPI.Header.Set("device", "3")
			reqAPI.Header.Set("version", "1.5")
			reqAPI.Header.Set("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64)")
			reqAPI.Header.Set("Accept", "application/json")
			reqAPI.Header.Set("Referer", htmlURL)
			for _, cookie := range cookies {
				reqAPI.AddCookie(cookie)
			}
			return client.Do(reqAPI)
		}
		apiURL := fmt.Sprintf("https://reviews.femaledaily.com/service_review/api/brand?type=product&sort=name&order=ASC&alphabet=%s&page=%s&limit=10", url.QueryEscape(alphabet), pageParam)
		respAPI, err := doRequest(apiURL)
		if err == nil && respAPI.StatusCode == 500 {
			respAPI.Body.Close()
			apiURL = fmt.Sprintf("https://reviews.femaledaily.com/service_review/api/brand?type=product&sort=name&order=ASC&page=%s&limit=10", pageParam)
			respAPI, err = doRequest(apiURL)
		}
		if err != nil || respAPI.StatusCode != 200 {
			c.IndentedJSON(http.StatusInternalServerError, gin.H{"error": "Gagal mengambil daftar brand dari API"})
			return
		}
		defer respAPI.Body.Close()
		bodyBytes, _ := io.ReadAll(respAPI.Body)
		apiJSON := string(bodyBytes)
		var brandListResp BrandListResponse
		brandListResp.Pagination = PaginationInfo{
			CurrentPage: gjson.Get(apiJSON, "payload.pagination.page").Int(),
			TotalItems:  gjson.Get(apiJSON, "payload.pagination.total").Int(),
			TotalPage:   gjson.Get(apiJSON, "payload.pagination.total_page").Int(),
		}
		brandsArray := gjson.Get(apiJSON, "payload.data").Array()
		for _, b := range brandsArray {
			brandListResp.Brands = append(brandListResp.Brands, BrandListItem{
				Name:        b.Get("name").String(),
				Slug:        b.Get("slug").String(),
				Logo:        b.Get("logo").String(),
				Type:        b.Get("type").String(),
				Description: b.Get("description").String(),
			})
		}
		c.IndentedJSON(http.StatusOK, gin.H{"status": "success", "data": brandListResp})
	})

	fmt.Println("🚀 Scraper API Female Daily berjalan di http://localhost:8080")
	r.Run(":8081")
}
