package services

import (
	"encoding/json"
	"net/url"

	dto "astrology-api/dto/astrology"
)

//////////////////////////////////////////////////////////////
// Panchang Service
//////////////////////////////////////////////////////////////

type PanchangService interface {
	Today(userID uint, req dto.PanchangRequest) (*dto.VedicDataResponse, error)
	Monthly(userID uint, req dto.PanchangRequest) (*dto.VedicDataResponse, error)
	Choghadiya(userID uint, req dto.PanchangRequest) (*dto.VedicDataResponse, error)
	Hora(userID uint, req dto.PanchangRequest) (*dto.VedicDataResponse, error)
	Festivals(userID uint, req dto.PanchangRequest) (*dto.VedicDataResponse, error)
	SunMoon(userID uint, req dto.PanchangRequest) (*dto.SunMoonResponse, error)
}

type panchangService struct {
	astrologyBase
}

func NewPanchangService() PanchangService {
	return &panchangService{astrologyBase: newAstrologyBase()}
}

//////////////////////////////////////////////////////////////
// Parameter Building
//////////////////////////////////////////////////////////////

func panchangParams(req dto.PanchangRequest) (url.Values, error) {

	date, _, err := todayIfEmpty(req.Date)

	if err != nil {
		return nil, err
	}

	clockTime := req.Time

	if clockTime == "" {
		clockTime = "12:00"
	}

	params := url.Values{}
	params.Set("date", date)
	params.Set("time", clockTime)
	params.Set("tz", FormatFloat(DefaultTimezone(req.Timezone)))
	params.Set("lat", FormatFloat(req.Latitude))
	params.Set("lon", FormatFloat(req.Longitude))
	params.Set("lang", req.Lang)

	return params, nil
}

func (s *panchangService) fetch(
	userID uint,
	path string,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	params, err := panchangParams(req)

	if err != nil {
		return nil, err
	}

	client, err := s.vedic()

	if err != nil {
		return nil, err
	}

	data, err := client.Get(path, params)

	if err != nil {
		return nil, err
	}

	charged, err := s.chargeForRead(userID, FlagPricePanchang, TxnPanchang)

	if err != nil {
		return nil, err
	}

	return &dto.VedicDataResponse{Charged: charged, Data: data}, nil
}

//////////////////////////////////////////////////////////////
// Lookups
//////////////////////////////////////////////////////////////

func (s *panchangService) Today(
	userID uint,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	return s.fetch(userID, "panchang/panchang", req)
}

func (s *panchangService) Monthly(
	userID uint,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	return s.fetch(userID, "panchang/monthly-panchang", req)
}

func (s *panchangService) Choghadiya(
	userID uint,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	return s.fetch(userID, "panchang/choghadiya-muhurta", req)
}

func (s *panchangService) Hora(
	userID uint,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	return s.fetch(userID, "panchang/hora-muhurta", req)
}

func (s *panchangService) Festivals(
	userID uint,
	req dto.PanchangRequest,
) (*dto.VedicDataResponse, error) {

	return s.fetch(userID, "panchang/festivals", req)
}

//////////////////////////////////////////////////////////////
// Sun & Moon
//////////////////////////////////////////////////////////////

// SunMoon fans out the five single-value endpoints the panchang header needs, so
// the app makes one call instead of five. A failing part comes back as null
// rather than failing the whole strip.
func (s *panchangService) SunMoon(
	userID uint,
	req dto.PanchangRequest,
) (*dto.SunMoonResponse, error) {

	params, err := panchangParams(req)

	if err != nil {
		return nil, err
	}

	client, err := s.vedic()

	if err != nil {
		return nil, err
	}

	get := func(path string) any {

		raw, err := client.Get(path, params)

		if err != nil {
			return nil
		}

		var value any

		if err := json.Unmarshal(raw, &value); err != nil {
			return nil
		}

		return value
	}

	response := &dto.SunMoonResponse{
		Sunrise:   get("panchang/sunrise"),
		Sunset:    get("panchang/sunset"),
		Moonrise:  get("panchang/moonrise"),
		Moonset:   get("panchang/moonset"),
		MoonPhase: get("panchang/moon-phase"),
	}

	if _, err := s.chargeForRead(userID, FlagPricePanchang, TxnPanchang); err != nil {
		return nil, err
	}

	return response, nil
}

// NewPanchangServiceForAstrologer is the same service for the astrologer routes,
// which never charge.
func NewPanchangServiceForAstrologer() PanchangService {
	return &panchangService{astrologyBase: freeAstrologyBase()}
}
