package linodego

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

	"github.com/linode/linodego/internal/parseabletime"
)

// LKEClusterStatus represents the status of an LKECluster
type LKEClusterStatus string

// LKEClusterStatus enums start with LKECluster
const (
	LKEClusterReady    LKEClusterStatus = "ready"
	LKEClusterNotReady LKEClusterStatus = "not_ready"
)

type LKEClusterStackType string

const (
	LKEClusterStackIPv4 LKEClusterStackType = "ipv4"
	LKEClusterDualStack LKEClusterStackType = "ipv4-ipv6"
)

// LKECluster represents a LKECluster object
type LKECluster struct {
	ID           int                    `json:"id"`
	Created      *time.Time             `json:"-"`
	Updated      *time.Time             `json:"-"`
	Label        string                 `json:"label"`
	Region       string                 `json:"region"`
	Status       LKEClusterStatus       `json:"status"`
	K8sVersion   string                 `json:"k8s_version"`
	Tags         []string               `json:"tags"`
	ControlPlane LKEClusterControlPlane `json:"control_plane"`

	// NOTE: Tier may not currently be available to all users and can only be used with v4beta.
	Tier string `json:"tier"`

	// NOTE: APLEnabled is currently in beta and may only function with API version v4beta.
	APLEnabled bool `json:"apl_enabled"`

	// NOTE: SubnetID, VpcID, and StackType may not currently be available to all users and can only be used with v4beta.
	SubnetID  int                 `json:"subnet_id"`
	VpcID     int                 `json:"vpc_id"`
	StackType LKEClusterStackType `json:"stack_type"`
}

// LKEClusterCreateOptions fields are those accepted by CreateLKECluster
type LKEClusterCreateOptions struct {
	NodePools    []LKENodePoolCreateOptions     `json:"node_pools"`
	Label        string                         `json:"label"`
	Region       string                         `json:"region"`
	K8sVersion   string                         `json:"k8s_version"`
	Tags         []string                       `json:"tags,omitempty"`
	ControlPlane *LKEClusterControlPlaneOptions `json:"control_plane,omitempty"`

	// NOTE: Tier may not currently be available to all users and can only be used with v4beta.
	Tier string `json:"tier,omitempty"`

	// NOTE: APLEnabled is currently in beta and may only function with API version v4beta.
	APLEnabled bool `json:"apl_enabled,omitempty"`

	// NOTE: SubnetID, VpcID, and StackType may not currently be available to all users and can only be used with v4beta.
	SubnetID  *int                 `json:"subnet_id,omitempty"`
	VpcID     *int                 `json:"vpc_id,omitempty"`
	StackType *LKEClusterStackType `json:"stack_type,omitempty"`
}

// LKEClusterUpdateOptions fields are those accepted by UpdateLKECluster
type LKEClusterUpdateOptions struct {
	K8sVersion   string                         `json:"k8s_version,omitempty"`
	Label        string                         `json:"label,omitempty"`
	Tags         *[]string                      `json:"tags,omitempty"`
	ControlPlane *LKEClusterControlPlaneOptions `json:"control_plane,omitempty"`
}

// LKEClusterAPIEndpoint fields are those returned by ListLKEClusterAPIEndpoints
type LKEClusterAPIEndpoint struct {
	Endpoint string `json:"endpoint"`
}

// LKEClusterKubeconfig fields are those returned by GetLKEClusterKubeconfig
type LKEClusterKubeconfig struct {
	KubeConfig string `json:"kubeconfig"` // Base64-encoded Kubeconfig file for this Cluster.
}

// LKEClusterDashboard fields are those returned by GetLKEClusterDashboard
type LKEClusterDashboard struct {
	URL string `json:"url"`
}

// LKEVersion fields are those returned by GetLKEVersion
type LKEVersion struct {
	ID string `json:"id"`
}

// LKETierVersion fields are those returned by GetLKETierVersion
// NOTE: It may not currently be available to all users and can only be used with v4beta.
type LKETierVersion struct {
	ID   string         `json:"id"`
	Tier LKEVersionTier `json:"tier"`
}

// LKEVersionTier enums represents different LKE tiers
type LKEVersionTier string

// LKEVersionTier enums start with LKEVersion
const (
	LKEVersionStandard   LKEVersionTier = "standard"
	LKEVersionEnterprise LKEVersionTier = "enterprise"
)

// LKEClusterRegenerateOptions fields are those accepted by RegenerateLKECluster
type LKEClusterRegenerateOptions struct {
	KubeConfig   bool `json:"kubeconfig"`
	ServiceToken bool `json:"servicetoken"`
}

// UnmarshalJSON implements the json.Unmarshaler interface
func (i *LKECluster) UnmarshalJSON(b []byte) error {
	type Mask LKECluster

	p := struct {
		*Mask

		Created *parseabletime.ParseableTime `json:"created"`
		Updated *parseabletime.ParseableTime `json:"updated"`
	}{
		Mask: (*Mask)(i),
	}

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

	i.Created = (*time.Time)(p.Created)
	i.Updated = (*time.Time)(p.Updated)

	return nil
}

// GetCreateOptions converts a LKECluster to LKEClusterCreateOptions for use in CreateLKECluster
func (i LKECluster) GetCreateOptions() (o LKEClusterCreateOptions) {
	o.Label = i.Label
	o.Region = i.Region
	o.K8sVersion = i.K8sVersion
	o.Tags = i.Tags

	isHA := i.ControlPlane.HighAvailability

	o.ControlPlane = &LKEClusterControlPlaneOptions{
		HighAvailability: &isHA,
		// ACL will not be populated in the control plane response
	}

	// @TODO copy NodePools?
	return o
}

// GetUpdateOptions converts a LKECluster to LKEClusterUpdateOptions for use in UpdateLKECluster
func (i LKECluster) GetUpdateOptions() (o LKEClusterUpdateOptions) {
	o.K8sVersion = i.K8sVersion
	o.Label = i.Label
	o.Tags = &i.Tags

	isHA := i.ControlPlane.HighAvailability

	o.ControlPlane = &LKEClusterControlPlaneOptions{
		HighAvailability: &isHA,
		// ACL will not be populated in the control plane response
	}

	return o
}

// ListLKEVersions lists the Kubernetes versions available through LKE. This endpoint is cached by default.
func (c *Client) ListLKEVersions(ctx context.Context, opts *ListOptions) ([]LKEVersion, error) {
	e := "lke/versions"

	endpoint, err := generateListCacheURL(e, opts)
	if err != nil {
		return nil, err
	}

	if result := c.getCachedResponse(endpoint); result != nil {
		return result.([]LKEVersion), nil
	}

	response, err := getPaginatedResults[LKEVersion](ctx, c, e, opts)
	if err != nil {
		return nil, err
	}

	c.addCachedResponse(endpoint, response, &cacheExpiryTime)

	return response, nil
}

// GetLKEVersion gets details about a specific LKE Version. This endpoint is cached by default.
func (c *Client) GetLKEVersion(ctx context.Context, version string) (*LKEVersion, error) {
	e := formatAPIPath("lke/versions/%s", version)

	if result := c.getCachedResponse(e); result != nil {
		result := result.(LKEVersion)
		return &result, nil
	}

	response, err := doGETRequest[LKEVersion](ctx, c, e)
	if err != nil {
		return nil, err
	}

	c.addCachedResponse(e, response, &cacheExpiryTime)

	return response, nil
}

// ListLKETierVersions lists all Kubernetes versions available given tier through LKE.
// NOTE: This endpoint may not currently be available to all users and can only be used with v4beta.
func (c *Client) ListLKETierVersions(ctx context.Context, tier string, opts *ListOptions) ([]LKETierVersion, error) {
	return getPaginatedResults[LKETierVersion](ctx, c, formatAPIPath("lke/tiers/%s/versions", tier), opts)
}

// GetLKETierVersion gets the details of a specific LKE tier version.
// NOTE: This endpoint may not currently be available to all users and can only be used with v4beta.
func (c *Client) GetLKETierVersion(ctx context.Context, tier string, versionID string) (*LKETierVersion, error) {
	return doGETRequest[LKETierVersion](ctx, c, formatAPIPath("lke/tiers/%s/versions/%s", tier, versionID))
}

// ListLKEClusterAPIEndpoints gets the API Endpoint for the LKE Cluster specified
func (c *Client) ListLKEClusterAPIEndpoints(ctx context.Context, clusterID int, opts *ListOptions) ([]LKEClusterAPIEndpoint, error) {
	return getPaginatedResults[LKEClusterAPIEndpoint](ctx, c, formatAPIPath("lke/clusters/%d/api-endpoints", clusterID), opts)
}

// ListLKEClusters lists LKEClusters
func (c *Client) ListLKEClusters(ctx context.Context, opts *ListOptions) ([]LKECluster, error) {
	return getPaginatedResults[LKECluster](ctx, c, "lke/clusters", opts)
}

// GetLKECluster gets the lkeCluster with the provided ID
func (c *Client) GetLKECluster(ctx context.Context, clusterID int) (*LKECluster, error) {
	e := formatAPIPath("lke/clusters/%d", clusterID)
	return doGETRequest[LKECluster](ctx, c, e)
}

// CreateLKECluster creates a LKECluster
func (c *Client) CreateLKECluster(ctx context.Context, opts LKEClusterCreateOptions) (*LKECluster, error) {
	return doPOSTRequest[LKECluster](ctx, c, "lke/clusters", opts)
}

// UpdateLKECluster updates the LKECluster with the specified id
func (c *Client) UpdateLKECluster(ctx context.Context, clusterID int, opts LKEClusterUpdateOptions) (*LKECluster, error) {
	e := formatAPIPath("lke/clusters/%d", clusterID)
	return doPUTRequest[LKECluster](ctx, c, e, opts)
}

// DeleteLKECluster deletes the LKECluster with the specified id
func (c *Client) DeleteLKECluster(ctx context.Context, clusterID int) error {
	e := formatAPIPath("lke/clusters/%d", clusterID)
	return doDELETERequest(ctx, c, e)
}

// GetLKEClusterKubeconfig gets the Kubeconfig for the LKE Cluster specified
func (c *Client) GetLKEClusterKubeconfig(ctx context.Context, clusterID int) (*LKEClusterKubeconfig, error) {
	e := formatAPIPath("lke/clusters/%d/kubeconfig", clusterID)
	return doGETRequest[LKEClusterKubeconfig](ctx, c, e)
}

// DeleteLKEClusterKubeconfig deletes the Kubeconfig for the LKE Cluster specified
func (c *Client) DeleteLKEClusterKubeconfig(ctx context.Context, clusterID int) error {
	e := formatAPIPath("lke/clusters/%d/kubeconfig", clusterID)
	return doDELETERequest(ctx, c, e)
}

// GetLKEClusterDashboard gets information about the dashboard for an LKE cluster
func (c *Client) GetLKEClusterDashboard(ctx context.Context, clusterID int) (*LKEClusterDashboard, error) {
	e := formatAPIPath("lke/clusters/%d/dashboard", clusterID)
	return doGETRequest[LKEClusterDashboard](ctx, c, e)
}

// RecycleLKEClusterNodes recycles all nodes in all pools of the specified LKE Cluster.
func (c *Client) RecycleLKEClusterNodes(ctx context.Context, clusterID int) error {
	e := formatAPIPath("lke/clusters/%d/recycle", clusterID)
	return doPOSTRequestNoRequestResponseBody(ctx, c, e)
}

// RegenerateLKECluster regenerates the Kubeconfig file and/or the service account token for the specified LKE Cluster.
func (c *Client) RegenerateLKECluster(ctx context.Context, clusterID int, opts LKEClusterRegenerateOptions) (*LKECluster, error) {
	e := formatAPIPath("lke/clusters/%d/regenerate", clusterID)
	return doPOSTRequest[LKECluster](ctx, c, e, opts)
}

// DeleteLKEClusterServiceToken deletes and regenerate the service account token for a Cluster.
func (c *Client) DeleteLKEClusterServiceToken(ctx context.Context, clusterID int) error {
	e := formatAPIPath("lke/clusters/%d/servicetoken", clusterID)
	return doDELETERequest(ctx, c, e)
}

// GetLKEClusterAPLConsoleURL gets the URL of this cluster's APL installation if this cluster is APL-enabled.
func (c *Client) GetLKEClusterAPLConsoleURL(ctx context.Context, clusterID int) (string, error) {
	cluster, err := c.GetLKECluster(ctx, clusterID)
	if err != nil {
		return "", err
	}

	if cluster.APLEnabled {
		return fmt.Sprintf("https://console.lke%d.akamai-apl.net", cluster.ID), nil
	}

	return "", nil
}

// GetLKEClusterAPLHealthCheckURL gets the URL of this cluster's APL health check endpoint if this cluster is APL-enabled.
func (c *Client) GetLKEClusterAPLHealthCheckURL(ctx context.Context, clusterID int) (string, error) {
	cluster, err := c.GetLKECluster(ctx, clusterID)
	if err != nil {
		return "", err
	}

	if cluster.APLEnabled {
		return fmt.Sprintf("https://auth.lke%d.akamai-apl.net/ready", cluster.ID), nil
	}

	return "", nil
}
