package kfa

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"service/pkg/logger"
)

type TokenManager interface {
	GetAccessToken() string
	ForceRefreshAndGetToken(ctx context.Context) (string, error)
}

type Config struct {
	InternalBaseURL string
	PageSize        int
}

type Worker struct {
	cfg          Config
	repo         CommandRepository
	tokenManager TokenManager
}

func NewWorker(cfg Config, repo CommandRepository, tokenManager TokenManager) *Worker {
	if cfg.PageSize == 0 {
		cfg.PageSize = 100
	}
	return &Worker{
		cfg:          cfg,
		repo:         repo,
		tokenManager: tokenManager,
	}
}

func (w *Worker) readTracker() int {
	b, err := os.ReadFile("internal/master/kfa/kfa_tracker.txt")
	if err != nil {
		return 1
	}
	p, err := strconv.Atoi(strings.TrimSpace(string(b)))
	if err != nil || p < 1 {
		return 1
	}
	return p
}

func (w *Worker) writeTracker(page int) {
	os.WriteFile("internal/master/kfa/kfa_tracker.txt", []byte(strconv.Itoa(page)), 0644)
}

func (w *Worker) Run(ctx context.Context) {
	page := w.readTracker()

	for {
		select {
		case <-ctx.Done():
			logger.Default().Info("KFA Master Puller stopped")
			return
		default:
			err := w.processPage(ctx, page)
			if err != nil {
				logger.Default().Error("KFA Master Puller error processing page", logger.ErrorField(err), logger.Int("page", page))
				time.Sleep(30 * time.Second)
				continue
			}

			page++
			w.writeTracker(page)
			time.Sleep(5 * time.Second) // Jedah halus antar halaman
		}
	}
}

func (w *Worker) processPage(ctx context.Context, page int) error {
	listURL := fmt.Sprintf("%s/satusehat/reference/kfa/products?page=%d&size=%d&product_type=farmasi", strings.TrimRight(w.cfg.InternalBaseURL, "/"), page, w.cfg.PageSize)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, listURL, nil)
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+w.tokenManager.GetAccessToken())
	req.Header.Set("Accept", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}

	if resp.StatusCode == http.StatusUnauthorized {
		resp.Body.Close()
		logger.Default().Warn("KFA Master Puller list API 401, refreshing token...", logger.Int("page", page))
		newToken, err := w.tokenManager.ForceRefreshAndGetToken(ctx)
		if err != nil {
			return err
		}

		req, _ = http.NewRequestWithContext(ctx, http.MethodGet, listURL, nil)
		req.Header.Set("Authorization", "Bearer "+newToken)
		req.Header.Set("Accept", "application/json")

		resp, err = client.Do(req)
		if err != nil {
			return err
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusTooManyRequests {
		logger.Default().Warn("[KFA WORKER] ⏸️ Rate limit (429) pada List API, jeda 30s & ulangi halaman", logger.Int("page", page))
		return fmt.Errorf("rate limited on list API") // Akan memicu waktu tidur 30 detik di method Run() dan mencoba halaman yang sama
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("list API returned status: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return err
	}

	var listRes KfaListResponse
	if err := json.Unmarshal(body, &listRes); err != nil {
		return err
	}

	if len(listRes.Data.Items.Data) == 0 {
		logger.Default().Info("No more KFA products found, resetting to page 1 and sleeping", logger.Int("page", page))
		w.writeTracker(1)
		time.Sleep(6 * time.Hour)
		return fmt.Errorf("empty page, reset triggered")
	}

	logger.Default().Info("Pulling KFA items", logger.Int("page", page), logger.Int("items", len(listRes.Data.Items.Data)))
	for _, item := range listRes.Data.Items.Data {
		for {
			rateLimited, err := w.fetchAndSaveDetail(ctx, item.KfaCode)
			if rateLimited {
				logger.Default().Warn("[KFA WORKER] ⏸️ Rate limit (429) dari Satu Sehat - jeda 60 detik dan retry dokumen yang sama", logger.String("kfa_code", item.KfaCode))
				time.Sleep(60 * time.Second)
				continue // Ulangi request untuk item ini (Tidak melewatkan ID)
			}
			if err != nil {
				logger.Default().Error("Failed to fetch/save KFA detail", logger.String("kfa_code", item.KfaCode), logger.ErrorField(err))
			}
			break // Lanjut ke item/ID berikutnya bila sukses atau gagal karena alasan non-ratelimit
		}
		time.Sleep(500 * time.Millisecond) // Pencegahan Rate Limit Part 3
	}

	return nil
}

func (w *Worker) fetchAndSaveDetail(ctx context.Context, kfaCode string) (bool, error) {
	detailURL := fmt.Sprintf("%s/satusehat/reference/kfa/products/%s", strings.TrimRight(w.cfg.InternalBaseURL, "/"), kfaCode)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)
	if err != nil {
		return false, err
	}
	req.Header.Set("Authorization", "Bearer "+w.tokenManager.GetAccessToken())
	req.Header.Set("Accept", "application/json")

	client := &http.Client{Timeout: 30 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return false, err
	}

	if resp.StatusCode == http.StatusUnauthorized {
		resp.Body.Close()
		newToken, err := w.tokenManager.ForceRefreshAndGetToken(ctx)
		if err != nil {
			return false, err
		}

		req, _ = http.NewRequestWithContext(ctx, http.MethodGet, detailURL, nil)
		req.Header.Set("Authorization", "Bearer "+newToken)
		req.Header.Set("Accept", "application/json")

		resp, err = client.Do(req)
		if err != nil {
			return false, err
		}
	}
	defer resp.Body.Close()

	if resp.StatusCode == http.StatusTooManyRequests {
		return true, fmt.Errorf("rate limited (429)")
	}

	if resp.StatusCode != http.StatusOK {
		return false, fmt.Errorf("detail API returned status: %d", resp.StatusCode)
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return false, err
	}

	var detailRes KfaDetailResponse
	if err := json.Unmarshal(body, &detailRes); err != nil {
		return false, err
	}

	product, ingredients, packagings := MapKfaDetailToEntity(detailRes.Data.Result)
	return false, w.repo.UpsertProductBundle(ctx, product, ingredients, packagings)
}
