package exchangerates

import (
	"math"
	"strings"
	"time"

	"github.com/mayswind/ezbookkeeping/pkg/core"
	"github.com/mayswind/ezbookkeeping/pkg/errs"
	"github.com/mayswind/ezbookkeeping/pkg/log"
	"github.com/mayswind/ezbookkeeping/pkg/models"
	"github.com/mayswind/ezbookkeeping/pkg/utils"
	"github.com/mayswind/ezbookkeeping/pkg/validators"
)

const czechNationalBankDailyExchangeRateUrl = "https://www.cnb.cz/en/financial-markets/foreign-exchange-market/central-bank-exchange-rate-fixing/central-bank-exchange-rate-fixing/daily.txt"
const czechNationalBankMonthlyOtherExchangeRateUrl = "https://www.cnb.cz/en/financial-markets/foreign-exchange-market/fx-rates-of-other-currencies/fx-rates-of-other-currencies/fx_rates.txt"
const czechNationalBankExchangeRateReferenceUrl = "https://www.cnb.cz/en/financial-markets/foreign-exchange-market/central-bank-exchange-rate-fixing/central-bank-exchange-rate-fixing/"
const czechNationalBankDataSource = "Česká národní banka"
const czechNationalBankBaseCurrency = "CZK"

const czechNationalBankDataUpdateDateFormat = "02 Jan 2006 15:04"
const czechNationalBankDataUpdateDateTimezone = "Europe/Prague"

// CzechNationalBankDataSource defines the structure of exchange rates data source of Czech National Bank
type CzechNationalBankDataSource struct {
	ExchangeRatesDataSource
}

// GetRequestUrls returns the czech nation bank data source urls
func (e *CzechNationalBankDataSource) GetRequestUrls() []string {
	return []string{czechNationalBankMonthlyOtherExchangeRateUrl, czechNationalBankDailyExchangeRateUrl}
}

// Parse returns the common response entity according to the czech nation bank data source raw response
func (e *CzechNationalBankDataSource) Parse(c *core.Context, content []byte) (*models.LatestExchangeRateResponse, error) {
	lines := strings.Split(string(content), "\n")

	if len(lines) < 3 {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] content is invalid, content is %s", string(content))
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	headerLineItems := strings.Split(lines[0], "#")

	if len(headerLineItems) != 2 {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] first line of content is invalid, content is %s", lines[0])
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	updateDate := strings.TrimSpace(headerLineItems[0])

	titleLineItems := strings.Split(lines[1], "|")
	titleItemMap := make(map[string]int)

	for i := 0; i < len(titleLineItems); i++ {
		titleItemMap[titleLineItems[i]] = i
	}

	currencyCodeColumnIndex, exists := titleItemMap["Code"]

	if !exists {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] missing currency code column in title line, title line is %s", lines[1])
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	amountColumnIndex, exists := titleItemMap["Amount"]

	if !exists {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] missing amount column in title line, title line is %s", lines[1])
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	rateColumnIndex, exists := titleItemMap["Rate"]

	if !exists {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] missing rate column in title line, title line is %s", lines[1])
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	exchangeRates := make(models.LatestExchangeRateSlice, 0, len(lines)-2)

	for i := 2; i < len(lines); i++ {
		line := strings.TrimSpace(lines[i])
		exchangeRate := e.parseExchangeRate(c, line, currencyCodeColumnIndex, amountColumnIndex, rateColumnIndex)

		if exchangeRate != nil {
			exchangeRates = append(exchangeRates, exchangeRate)
		}
	}

	timezone, err := time.LoadLocation(czechNationalBankDataUpdateDateTimezone)

	if err != nil {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] failed to get timezone, timezone name is %s", czechNationalBankDataUpdateDateTimezone)
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	updateDateTime := updateDate + " 14:30" // Exchange rates of commonly traded currencies are declared every working day after 2.30 p.m.
	updateTime, err := time.ParseInLocation(czechNationalBankDataUpdateDateFormat, updateDateTime, timezone)

	if err != nil {
		log.ErrorfWithRequestId(c, "[czech_national_bank_datasource.Parse] failed to parse update date, datetime is %s", updateDateTime)
		return nil, errs.ErrFailedToRequestRemoteApi
	}

	latestExchangeRateResp := &models.LatestExchangeRateResponse{
		DataSource:    czechNationalBankDataSource,
		ReferenceUrl:  czechNationalBankExchangeRateReferenceUrl,
		UpdateTime:    updateTime.Unix(),
		BaseCurrency:  czechNationalBankBaseCurrency,
		ExchangeRates: exchangeRates,
	}

	return latestExchangeRateResp, nil
}

func (e *CzechNationalBankDataSource) parseExchangeRate(c *core.Context, line string, currencyCodeColumnIndex int, amountColumnIndex int, rateColumnIndex int) *models.LatestExchangeRate {
	if len(line) < 1 {
		return nil
	}

	items := strings.Split(line, "|")

	if currencyCodeColumnIndex >= len(items) || amountColumnIndex >= len(items) || rateColumnIndex >= len(items) {
		log.WarnfWithRequestId(c, "[czech_national_bank_datasource.parseExchangeRate] missing column in data line, line is %s", line)
		return nil
	}

	currencyCode := items[currencyCodeColumnIndex]

	if _, exists := validators.AllCurrencyNames[currencyCode]; !exists {
		return nil
	}

	amount, err := utils.StringToInt64(items[amountColumnIndex])

	if err != nil {
		log.WarnfWithRequestId(c, "[czech_national_bank_datasource.parseExchangeRate] failed to parse amount, line is %s", line)
		return nil
	}

	rate, err := utils.StringToFloat64(items[rateColumnIndex])

	if err != nil {
		log.WarnfWithRequestId(c, "[czech_national_bank_datasource.parseExchangeRate] failed to parse rate, line is %s", line)
		return nil
	}

	if rate <= 0 {
		log.WarnfWithRequestId(c, "[czech_national_bank_datasource.parseExchangeRate] rate is invalid, line is %s", line)
		return nil
	}

	finalRate := float64(amount) / rate

	if math.IsInf(finalRate, 0) {
		return nil
	}

	return &models.LatestExchangeRate{
		Currency: currencyCode,
		Rate:     utils.Float64ToString(finalRate),
	}
}
