package worker

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"service/internal/auth"
	"service/pkg/logger"
	"strings"
	"time"
)

// apiLoginResponse is a helper struct to unmarshal the nested API response.
type apiLoginResponse struct {
	Data auth.LoginResponse `json:"data"`
}

// login performs the initial login to get the first set of tokens.
func (m *Manager) login(ctx context.Context) error {
	loginURL := fmt.Sprintf("%s/auth/login", strings.TrimRight(m.cfg.SatuSehat.InternalFHIRServerURL, "/"))

	reqBody := map[string]string{
		"email":    "admin@example.com",
		"password": "password123",
	}
	payloadBytes, _ := json.Marshal(reqBody)

	req, err := http.NewRequestWithContext(ctx, "POST", loginURL, bytes.NewBuffer(payloadBytes))
	if err != nil {
		return fmt.Errorf("gagal membuat login request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	logger.Default().Info("[WORKER AUTH] Melakukan login untuk mendapatkan token awal...", logger.String("url", loginURL))

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("gagal melakukan login request: %w", err)
	}
	defer resp.Body.Close()

	// Baca body untuk logging jika terjadi error
	bodyBytes, readErr := io.ReadAll(resp.Body)
	if readErr != nil {
		return fmt.Errorf("gagal membaca response body login: %w", readErr)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("login gagal dengan status: %d, response: %s", resp.StatusCode, string(bodyBytes))
	}

	var apiResp apiLoginResponse
	if err := json.Unmarshal(bodyBytes, &apiResp); err != nil {
		return fmt.Errorf("gagal decode response login: %w, body: %s", err, string(bodyBytes))
	}
	loginResp := apiResp.Data

	if loginResp.AccessToken == "" || loginResp.RefreshToken == "" {
		logger.Default().Error("[WORKER AUTH] Token kosong diterima dari server", logger.Any("response", loginResp))
		return fmt.Errorf("access token atau refresh token kosong diterima dari server")
	}

	m.accessToken = loginResp.AccessToken
	m.refreshToken = loginResp.RefreshToken
	logger.Default().Info("[WORKER AUTH] Berhasil mendapatkan token awal.")
	return nil
}

// refresh uses the refresh_token to get a new access_token.
func (m *Manager) refresh(ctx context.Context) error {
	refreshURL := fmt.Sprintf("%s/auth/refresh", strings.TrimRight(m.cfg.SatuSehat.InternalFHIRServerURL, "/"))

	if m.refreshToken == "" {
		return fmt.Errorf("tidak ada refresh token untuk digunakan, akan mencoba login ulang")
	}

	reqBody := auth.RefreshTokenRequest{
		RefreshToken: m.refreshToken,
		Provider:     "jwt",
	}
	payloadBytes, _ := json.Marshal(reqBody)

	req, err := http.NewRequestWithContext(ctx, "POST", refreshURL, bytes.NewBuffer(payloadBytes))
	if err != nil {
		return fmt.Errorf("gagal membuat refresh request: %w", err)
	}
	req.Header.Set("Content-Type", "application/json")

	logger.Default().Info("[WORKER AUTH] Melakukan refresh token...", logger.String("url", refreshURL))

	client := &http.Client{Timeout: 15 * time.Second}
	resp, err := client.Do(req)
	if err != nil {
		return fmt.Errorf("gagal melakukan refresh request: %w", err)
	}
	defer resp.Body.Close()

	bodyBytes, readErr := io.ReadAll(resp.Body)
	if readErr != nil {
		return fmt.Errorf("gagal membaca response body refresh: %w", readErr)
	}

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("refresh token gagal dengan status: %d, response: %s", resp.StatusCode, string(bodyBytes))
	}

	var apiResp apiLoginResponse
	if err := json.Unmarshal(bodyBytes, &apiResp); err != nil {
		return fmt.Errorf("gagal decode response refresh: %w, body: %s", err, string(bodyBytes))
	}
	refreshResp := apiResp.Data

	if refreshResp.AccessToken == "" {
		logger.Default().Error("[WORKER AUTH] Access token kosong diterima saat refresh", logger.Any("response", refreshResp))
		return fmt.Errorf("access token kosong diterima dari server saat refresh")
	}

	m.accessToken = refreshResp.AccessToken
	// Update refresh token if a new one is provided
	if refreshResp.RefreshToken != "" {
		m.refreshToken = refreshResp.RefreshToken
	}
	logger.Default().Info("[WORKER AUTH] Berhasil me-refresh token.")
	return nil
}

// GetAccessToken safely retrieves the current access token.
func (m *Manager) GetAccessToken() string {
	m.tokenMutex.RLock()
	defer m.tokenMutex.RUnlock()
	return m.accessToken
}

// ForceRefreshAndGetToken tries to refresh the token, falls back to login, and returns the new token.
func (m *Manager) ForceRefreshAndGetToken(ctx context.Context) (string, error) {
	m.tokenMutex.Lock()
	defer m.tokenMutex.Unlock()

	if err := m.refresh(ctx); err != nil {
		logger.Default().Warn("[WORKER AUTH] Refresh token gagal, mencoba login ulang...", logger.ErrorField(err))
		if loginErr := m.login(ctx); loginErr != nil {
			return "", fmt.Errorf("fallback login juga gagal: %w", loginErr)
		}
	}
	return m.accessToken, nil
}
