package scw

import (
	"encoding/json"
	"fmt"
	"strings"

	"github.com/scaleway/scaleway-sdk-go/errors"
	"github.com/scaleway/scaleway-sdk-go/logger"
	"github.com/scaleway/scaleway-sdk-go/validation"
)

// localityPartsSeparator is the separator used in Zone and Region
const localityPartsSeparator = "-"

// Zone is an availability zone
type Zone string

const (
	// ZoneFrPar1 represents the fr-par-1 zone
	ZoneFrPar1 = Zone("fr-par-1")
	// ZoneFrPar2 represents the fr-par-2 zone
	ZoneFrPar2 = Zone("fr-par-2")
	// ZoneFrPar3 represents the fr-par-3 zone
	ZoneFrPar3 = Zone("fr-par-3")
	// ZoneNlAms1 represents the nl-ams-1 zone
	ZoneNlAms1 = Zone("nl-ams-1")
	// ZoneNlAms2 represents the nl-ams-2 zone
	ZoneNlAms2 = Zone("nl-ams-2")
	// ZoneNlAms3 represents the nl-ams-3 zone
	ZoneNlAms3 = Zone("nl-ams-3")
	// ZonePlWaw1 represents the pl-waw-1 zone
	ZonePlWaw1 = Zone("pl-waw-1")
	// ZonePlWaw2 represents the pl-waw-2 zone
	ZonePlWaw2 = Zone("pl-waw-2")
	// ZonePlWaw3 represents the pl-waw-3 zone
	ZonePlWaw3 = Zone("pl-waw-3")
	// ZoneItMil1 represents the it-mil-1 zone
	ZoneItMil1 = Zone("it-mil-1")
)

// AllZones is an array that list all zones
var AllZones = []Zone{
	ZoneFrPar1,
	ZoneFrPar2,
	ZoneFrPar3,
	ZoneNlAms1,
	ZoneNlAms2,
	ZoneNlAms3,
	ZonePlWaw1,
	ZonePlWaw2,
	ZonePlWaw3,
	ZoneItMil1,
}

// Exists checks whether a zone exists
func (zone Zone) Exists() bool {
	for _, z := range AllZones {
		if z == zone {
			return true
		}
	}
	return false
}

// String returns a Zone as a string
func (zone Zone) String() string {
	return string(zone)
}

// Region returns the parent Region for the Zone.
// Manipulates the string directly to allow unlisted zones formatted as xx-yyy-z.
func (zone Zone) Region() (Region, error) {
	zoneStr := zone.String()
	if !validation.IsZone(zoneStr) {
		return "", fmt.Errorf("invalid zone '%v'", zoneStr)
	}
	zoneParts := strings.Split(zoneStr, localityPartsSeparator)
	return Region(strings.Join(zoneParts[:2], localityPartsSeparator)), nil
}

// Region is a geographical location
type Region string

const (
	// RegionFrPar represents the fr-par region
	RegionFrPar = Region("fr-par")
	// RegionNlAms represents the nl-ams region
	RegionNlAms = Region("nl-ams")
	// RegionPlWaw represents the pl-waw region
	RegionPlWaw = Region("pl-waw")
	// RegionItMil represents the it-mil region
	RegionItMil = Region("it-mil")
)

// AllRegions is an array that list all regions
var AllRegions = []Region{
	RegionFrPar,
	RegionNlAms,
	RegionPlWaw,
	RegionItMil,
}

// Exists checks whether a region exists
func (region Region) Exists() bool {
	for _, r := range AllRegions {
		if r == region {
			return true
		}
	}
	return false
}

// GetZones is a function that returns the zones for the specified region
func (region Region) GetZones() []Zone {
	switch region {
	case RegionFrPar:
		return []Zone{ZoneFrPar1, ZoneFrPar2, ZoneFrPar3}
	case RegionNlAms:
		return []Zone{ZoneNlAms1, ZoneNlAms2, ZoneNlAms3}
	case RegionPlWaw:
		return []Zone{ZonePlWaw1, ZonePlWaw2, ZonePlWaw3}
	case RegionItMil:
		return []Zone{ZoneItMil1}
	default:
		return []Zone{}
	}
}

// ParseZone parses a string value into a Zone and returns an error if it has a bad format.
func ParseZone(zone string) (Zone, error) {
	switch zone {
	case "par1":
		// would be triggered by API market place
		// logger.Warningf("par1 is a deprecated name for zone, use fr-par-1 instead")
		return ZoneFrPar1, nil
	case "ams1":
		// would be triggered by API market place
		// logger.Warningf("ams1 is a deprecated name for zone, use nl-ams-1 instead")
		return ZoneNlAms1, nil
	default:
		if !validation.IsZone(zone) {
			zones := []string(nil)
			for _, z := range AllZones {
				zones = append(zones, string(z))
			}
			return "", errors.New("bad zone format, available zones are: %s", strings.Join(zones, ", "))
		}

		newZone := Zone(zone)
		if !newZone.Exists() {
			logger.Infof("%s is an unknown zone\n", newZone)
		}
		return newZone, nil
	}
}

// UnmarshalJSON implements the Unmarshaler interface for a Zone.
// this to call ParseZone on the string input and return the correct Zone object.
func (zone *Zone) UnmarshalJSON(input []byte) error {
	// parse input value as string
	var stringValue string
	err := json.Unmarshal(input, &stringValue)
	if err != nil {
		return err
	}

	// New fields may carry empty value
	if stringValue == "" {
		return nil
	}

	// parse string as Zone
	*zone, err = ParseZone(stringValue)
	if err != nil {
		return err
	}
	return nil
}

// ParseRegion parses a string value into a Region and returns an error if it has a bad format.
func ParseRegion(region string) (Region, error) {
	switch region {
	case "par1":
		// would be triggered by API market place
		// logger.Warningf("par1 is a deprecated name for region, use fr-par instead")
		return RegionFrPar, nil
	case "ams1":
		// would be triggered by API market place
		// logger.Warningf("ams1 is a deprecated name for region, use nl-ams instead")
		return RegionNlAms, nil
	default:
		if !validation.IsRegion(region) {
			regions := []string(nil)
			for _, r := range AllRegions {
				regions = append(regions, string(r))
			}
			return "", errors.New("bad region format, available regions are: %s", strings.Join(regions, ", "))
		}

		newRegion := Region(region)
		if !newRegion.Exists() {
			logger.Infof("%s is an unknown region\n", newRegion)
		}
		return newRegion, nil
	}
}

// UnmarshalJSON implements the Unmarshaler interface for a Region.
// this to call ParseRegion on the string input and return the correct Region object.
func (region *Region) UnmarshalJSON(input []byte) error {
	// parse input value as string
	var stringValue string
	err := json.Unmarshal(input, &stringValue)
	if err != nil {
		return err
	}

	// New fields may carry empty value
	if stringValue == "" {
		return nil
	}

	// parse string as Region
	*region, err = ParseRegion(stringValue)
	if err != nil {
		return err
	}
	return nil
}

// String returns a Region as a string
func (region Region) String() string {
	return string(region)
}
