package api

import (
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"sync"

	"github.com/mayswind/ezbookkeeping/pkg/core"
	"github.com/mayswind/ezbookkeeping/pkg/errs"
	"github.com/mayswind/ezbookkeeping/pkg/httpclient"
	"github.com/mayswind/ezbookkeeping/pkg/settings"
)

const openStreetMapTileImageUrlFormat = "https://tile.openstreetmap.org/{z}/{x}/{y}.png"                                                                                                                              // https://tile.openstreetmap.org/{z}/{x}/{y}.png
const openStreetMapHumanitarianStyleTileImageUrlFormat = "https://a.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png"                                                                                                        // https://{s}.tile.openstreetmap.fr/hot/{z}/{x}/{y}.png
const openTopoMapTileImageUrlFormat = "https://tile.opentopomap.org/{z}/{x}/{y}.png"                                                                                                                                  // https://tile.opentopomap.org/{z}/{x}/{y}.png
const opnvKarteMapTileImageUrlFormat = "https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png"                                                                                                                       // https://tileserver.memomaps.de/tilegen/{z}/{x}/{y}.png
const cyclOSMMapTileImageUrlFormat = "https://a.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png"                                                                                                                // https://{s}.tile-cyclosm.openstreetmap.fr/cyclosm/{z}/{x}/{y}.png
const cartoDBMapTileImageUrlFormat = "https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}{scale}.png"                                                                                                     // https://{s}.basemaps.cartocdn.com/{style}/{z}/{x}/{y}{scale}.png
const tomtomMapTileImageUrlFormat = "https://api.tomtom.com/map/1/tile/basic/main/{z}/{x}/{y}.png"                                                                                                                    // https://api.tomtom.com/map/{versionNumber}/tile/{layer}/{style}/{z}/{x}/{y}.png?key={key}&language={language}
const tianDiTuMapTileImageUrlFormat = "https://t0.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}"  // https://t{s}.tianditu.gov.cn/vec_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=vec&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key}
const tianDiTuMapAnnotationUrlFormat = "https://t0.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}" // https://t{s}.tianditu.gov.cn/cva_w/wmts?SERVICE=WMTS&REQUEST=GetTile&VERSION=1.0.0&LAYER=cva&STYLE=default&TILEMATRIXSET=w&FORMAT=tiles&TILEMATRIX={z}&TILEROW={y}&TILECOL={x}&tk={key}

// MapImageProxy represents map image proxy
type MapImageProxy struct {
	ApiUsingConfig
	mutex     sync.Mutex
	transport *http.Transport
}

// Initialize a map image proxy singleton instance
var (
	MapImages = &MapImageProxy{
		ApiUsingConfig: ApiUsingConfig{
			container: settings.Container,
		},
	}
)

func (p *MapImageProxy) initializeHttpTransport() {
	p.mutex.Lock()
	defer p.mutex.Unlock()

	if p.transport != nil {
		return
	}

	p.transport = http.DefaultTransport.(*http.Transport).Clone()
	httpclient.SetProxyUrl(p.transport, p.CurrentConfig().MapProxy)
}

// MapTileImageProxyHandler returns map tile image
func (p *MapImageProxy) MapTileImageProxyHandler(c *core.WebContext) (*httputil.ReverseProxy, *errs.Error) {
	return p.mapImageProxyHandler(c, func(c *core.WebContext, mapProvider string) (string, *errs.Error) {
		if mapProvider == settings.OpenStreetMapProvider {
			return openStreetMapTileImageUrlFormat, nil
		} else if mapProvider == settings.OpenStreetMapHumanitarianStyleProvider {
			return openStreetMapHumanitarianStyleTileImageUrlFormat, nil
		} else if mapProvider == settings.OpenTopoMapProvider {
			return openTopoMapTileImageUrlFormat, nil
		} else if mapProvider == settings.OPNVKarteMapProvider {
			return opnvKarteMapTileImageUrlFormat, nil
		} else if mapProvider == settings.CyclOSMMapProvider {
			return cyclOSMMapTileImageUrlFormat, nil
		} else if mapProvider == settings.CartoDBMapProvider {
			return cartoDBMapTileImageUrlFormat, nil
		} else if mapProvider == settings.TomTomMapProvider {
			targetUrl := tomtomMapTileImageUrlFormat + "?key=" + p.CurrentConfig().TomTomMapAPIKey
			language := c.Query("language")

			if language != "" {
				targetUrl = targetUrl + "&language=" + language
			}

			return targetUrl, nil
		} else if mapProvider == settings.TianDiTuProvider {
			return tianDiTuMapTileImageUrlFormat + "&tk=" + p.CurrentConfig().TianDiTuAPIKey, nil
		} else if mapProvider == settings.CustomProvider {
			return p.CurrentConfig().CustomMapTileServerTileLayerUrl, nil
		}

		return "", errs.ErrParameterInvalid
	})
}

// MapAnnotationImageProxyHandler returns map annotation image
func (p *MapImageProxy) MapAnnotationImageProxyHandler(c *core.WebContext) (*httputil.ReverseProxy, *errs.Error) {
	return p.mapImageProxyHandler(c, func(c *core.WebContext, mapProvider string) (string, *errs.Error) {
		if mapProvider == settings.TianDiTuProvider {
			return tianDiTuMapAnnotationUrlFormat + "&tk=" + p.CurrentConfig().TianDiTuAPIKey, nil
		} else if mapProvider == settings.CustomProvider {
			return p.CurrentConfig().CustomMapTileServerAnnotationLayerUrl, nil
		}

		return "", errs.ErrParameterInvalid
	})
}

func (p *MapImageProxy) mapImageProxyHandler(c *core.WebContext, fn func(c *core.WebContext, mapProvider string) (string, *errs.Error)) (*httputil.ReverseProxy, *errs.Error) {
	mapProvider := strings.Replace(c.Query("provider"), "-", "_", -1)
	targetUrl := ""

	if mapProvider != p.CurrentConfig().MapProvider {
		return nil, errs.ErrMapProviderNotCurrent
	}

	zoomLevel := c.Param("zoomLevel")
	coordinateX := c.Param("coordinateX")
	fileName := c.Param("fileName")
	fileNameParts := strings.Split(fileName, ".")
	coordinateY := fileNameParts[0]
	scale := c.Query("scale")

	if len(fileNameParts) != 2 || fileNameParts[len(fileNameParts)-1] != "png" {
		return nil, errs.ErrImageExtensionNotSupported
	}

	var err *errs.Error
	targetUrl, err = fn(c, mapProvider)

	if err != nil {
		return nil, err
	}

	if p.transport == nil {
		p.initializeHttpTransport()
	}

	director := func(req *http.Request) {
		imageRawUrl := targetUrl
		imageRawUrl = strings.Replace(imageRawUrl, "{z}", zoomLevel, -1)
		imageRawUrl = strings.Replace(imageRawUrl, "{x}", coordinateX, -1)
		imageRawUrl = strings.Replace(imageRawUrl, "{y}", coordinateY, -1)
		imageRawUrl = strings.Replace(imageRawUrl, "{scale}", scale, -1)
		imageUrl, _ := url.Parse(imageRawUrl)

		req.URL = imageUrl
		req.RequestURI = req.URL.RequestURI()
		req.Host = imageUrl.Host
	}

	return &httputil.ReverseProxy{
		Transport: p.transport,
		Director:  director,
	}, nil
}
