package coingecko

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
)

var ErrCoinNotFound = fmt.Errorf("coin not found")
var ErrTooManyRequests = fmt.Errorf("too many requests")

type CoinGeckoAPI struct {
	ApiURL     string
	HeaderKey  string
	ApiKey     string
	tokenCache map[string]TokenItem
}

type TokenItem struct {
	Id       string
	Chain    string
	Symbol   string
	Decimals int
}

func NewCoinGeckoAPI(url, headerKey string, apiKey string) *CoinGeckoAPI {
	return &CoinGeckoAPI{
		ApiURL:     url,
		HeaderKey:  headerKey,
		ApiKey:     apiKey,
		tokenCache: make(map[string]TokenItem),
	}
}

func (cg *CoinGeckoAPI) GetSymbolDailyPrice(coinID, days string) (*CoinHistoryResponse, error) {

	url := fmt.Sprintf("%s/api/v3/coins/%s/market_chart?vs_currency=usd&days=%s&interval=daily", cg.ApiURL, coinID, days)
	method := "GET"

	client := &http.Client{}
	req, err := http.NewRequest(method, url, nil)
	if err != nil {
		return nil, err
	}

	if cg.HeaderKey != "" && cg.ApiKey != "" {
		req.Header.Add(cg.HeaderKey, cg.ApiKey)
	}

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

	if res.StatusCode != 200 {
		switch res.StatusCode {
		case 404:
			return nil, ErrCoinNotFound
		case 429:
			return nil, ErrTooManyRequests
		default:
			return nil, fmt.Errorf("failed request with status code; %d", res.StatusCode)
		}
	}

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

	var td CoinHistoryResponse
	err = json.Unmarshal(body, &td)
	if err != nil {
		return nil, err
	}

	return &td, nil

}

func (cg *CoinGeckoAPI) GetSymbol(ChainId string, ContractId string) (string, error) {

	// lookup on cache first
	if val, ok := cg.tokenCache[ContractId]; ok {
		return val.Symbol, nil
	}

	// lookup on coingecko
	ti, err := cg.GetSymbolByContract(ChainId, ContractId)
	if err != nil {

		// if not found, return none
		if err.Error() == "token not found" {
			return "none", nil
		}

		return "", err

	}

	// add to cache
	fmt.Printf("adding to cache: %s\n", ti.Symbol)
	cg.tokenCache[ContractId] = *ti
	return ti.Symbol, nil

}

func (cg *CoinGeckoAPI) convertChain(chain string) string {

	// check if chain exsist on map
	if val, ok := convertionMap[chain]; ok {
		return val
	}
	return chain

}

// GetSymbolByContract returns the symbol of the token
// Input: ChaindId is the name of the chain: ie: ethereum, solana, etc
// Input: ContractId is the contract address of the token (ECR-20 or other)
func (cg *CoinGeckoAPI) GetSymbolByContract(ChainId string, ContractId string) (*TokenItem, error) {

	chain := cg.convertChain(ChainId)
	//url := "https://api.coingecko.com/api/v3/coins/avalanche/contract/0x2b2c81e08f1af8835a78bb2a90ae924ace0ea4be"
	url := fmt.Sprintf("https://%s/api/v3/coins/%s/contract/%s", cg.ApiURL, chain, ContractId)
	method := "GET"

	client := &http.Client{}
	req, err := http.NewRequest(method, url, nil)

	if err != nil {
		return nil, err
	}
	//req.Header.Add("Cookie", "__cf_bm=jUWxA1U8U3SdvDF2EXgCZUmnDopOozWnB5VpXIjWH.c-1682970763-0-AaLD4yVrSy53aAJQwVNe61P5IcXSnW4vIMeRrsRDIMGJ/+PbEcOv/lene34+FB4Q4kapT//4660lx/Rw507zw7Q=")

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

	if res.StatusCode == 404 {
		return nil, fmt.Errorf("token not found")
	}

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

	var td TokenData
	err = json.Unmarshal(body, &td)
	if err != nil {
		return nil, err
	}

	ti := TokenItem{
		Id:     td.ID,
		Chain:  ChainId,
		Symbol: td.Symbol,
	}

	fmt.Printf("\"%s\": \"%s\",\n", ContractId, ti.Symbol)

	return &ti, nil
}
