// Copyright 2023 The frp Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v1

import (
	"reflect"
	"slices"

	"github.com/samber/lo"

	"github.com/fatedier/frp/pkg/util/jsonx"
	"github.com/fatedier/frp/pkg/util/util"
)

const (
	PluginHTTP2HTTPS         = "http2https"
	PluginHTTP2HTTPSRedirect = "http2https_redirect"
	PluginHTTPProxy          = "http_proxy"
	PluginHTTPS2HTTP         = "https2http"
	PluginHTTPS2HTTPS        = "https2https"
	PluginHTTP2HTTP          = "http2http"
	PluginSocks5             = "socks5"
	PluginStaticFile         = "static_file"
	PluginUnixDomainSocket   = "unix_domain_socket"
	PluginTLS2Raw            = "tls2raw"
	PluginVirtualNet         = "virtual_net"
)

var clientPluginOptionsTypeMap = map[string]reflect.Type{
	PluginHTTP2HTTPS:         reflect.TypeFor[HTTP2HTTPSPluginOptions](),
	PluginHTTP2HTTPSRedirect: reflect.TypeFor[HTTP2HTTPSRedirectPluginOptions](),
	PluginHTTPProxy:          reflect.TypeFor[HTTPProxyPluginOptions](),
	PluginHTTPS2HTTP:         reflect.TypeFor[HTTPS2HTTPPluginOptions](),
	PluginHTTPS2HTTPS:        reflect.TypeFor[HTTPS2HTTPSPluginOptions](),
	PluginHTTP2HTTP:          reflect.TypeFor[HTTP2HTTPPluginOptions](),
	PluginSocks5:             reflect.TypeFor[Socks5PluginOptions](),
	PluginStaticFile:         reflect.TypeFor[StaticFilePluginOptions](),
	PluginUnixDomainSocket:   reflect.TypeFor[UnixDomainSocketPluginOptions](),
	PluginTLS2Raw:            reflect.TypeFor[TLS2RawPluginOptions](),
	PluginVirtualNet:         reflect.TypeFor[VirtualNetPluginOptions](),
}

type ClientPluginOptions interface {
	Complete()
	Clone() ClientPluginOptions
}

type TypedClientPluginOptions struct {
	Type string `json:"type"`
	ClientPluginOptions
}

func (c TypedClientPluginOptions) Clone() TypedClientPluginOptions {
	out := c
	if c.ClientPluginOptions != nil {
		out.ClientPluginOptions = c.ClientPluginOptions.Clone()
	}
	return out
}

func (c *TypedClientPluginOptions) UnmarshalJSON(b []byte) error {
	decoded, err := DecodeClientPluginOptionsJSON(b, DecodeOptions{})
	if err != nil {
		return err
	}
	*c = decoded
	return nil
}

func (c *TypedClientPluginOptions) MarshalJSON() ([]byte, error) {
	return jsonx.Marshal(c.ClientPluginOptions)
}

// AutoTLSOptions configures automatic certificate provisioning (ACME) for plugins
// that terminate TLS locally.
type AutoTLSOptions struct {
	Enable bool `json:"enable,omitempty"`
	// Contact email for certificate expiration and important notices.
	Email string `json:"email,omitempty"`
	// Directory used to cache ACME account and certificates.
	CacheDir string `json:"cacheDir,omitempty"`
	// ACME directory URL, e.g. Let's Encrypt staging/prod endpoint.
	CADirURL string `json:"caDirURL,omitempty"`
	// Restrict certificate issuance to the listed domains.
	HostAllowList []string `json:"hostAllowList,omitempty"`
}

func (o *AutoTLSOptions) Clone() *AutoTLSOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.HostAllowList = slices.Clone(o.HostAllowList)
	return &out
}

type HTTP2HTTPSPluginOptions struct {
	Type              string           `json:"type,omitempty"`
	LocalAddr         string           `json:"localAddr,omitempty"`
	HostHeaderRewrite string           `json:"hostHeaderRewrite,omitempty"`
	RequestHeaders    HeaderOperations `json:"requestHeaders,omitempty"`
}

func (o *HTTP2HTTPSPluginOptions) Complete() {}

func (o *HTTP2HTTPSPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.RequestHeaders = o.RequestHeaders.Clone()
	return &out
}

type HTTP2HTTPSRedirectPluginOptions struct {
	Type      string `json:"type,omitempty"`
	HTTPSPort int    `json:"httpsPort,omitempty"`
}

func (o *HTTP2HTTPSRedirectPluginOptions) Complete() {}

func (o *HTTP2HTTPSRedirectPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}

type HTTPProxyPluginOptions struct {
	Type         string `json:"type,omitempty"`
	HTTPUser     string `json:"httpUser,omitempty"`
	HTTPPassword string `json:"httpPassword,omitempty"`
}

func (o *HTTPProxyPluginOptions) Complete() {}

func (o *HTTPProxyPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}

type HTTPS2HTTPPluginOptions struct {
	Type              string           `json:"type,omitempty"`
	LocalAddr         string           `json:"localAddr,omitempty"`
	HostHeaderRewrite string           `json:"hostHeaderRewrite,omitempty"`
	RequestHeaders    HeaderOperations `json:"requestHeaders,omitempty"`
	EnableHTTP2       *bool            `json:"enableHTTP2,omitempty"`
	CrtPath           string           `json:"crtPath,omitempty"`
	KeyPath           string           `json:"keyPath,omitempty"`
	AutoTLS           *AutoTLSOptions  `json:"autoTLS,omitempty"`
}

func (o *HTTPS2HTTPPluginOptions) Complete() {
	o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
}

func (o *HTTPS2HTTPPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.RequestHeaders = o.RequestHeaders.Clone()
	out.EnableHTTP2 = util.ClonePtr(o.EnableHTTP2)
	out.AutoTLS = o.AutoTLS.Clone()
	return &out
}

type HTTPS2HTTPSPluginOptions struct {
	Type              string           `json:"type,omitempty"`
	LocalAddr         string           `json:"localAddr,omitempty"`
	HostHeaderRewrite string           `json:"hostHeaderRewrite,omitempty"`
	RequestHeaders    HeaderOperations `json:"requestHeaders,omitempty"`
	EnableHTTP2       *bool            `json:"enableHTTP2,omitempty"`
	CrtPath           string           `json:"crtPath,omitempty"`
	KeyPath           string           `json:"keyPath,omitempty"`
	AutoTLS           *AutoTLSOptions  `json:"autoTLS,omitempty"`
}

func (o *HTTPS2HTTPSPluginOptions) Complete() {
	o.EnableHTTP2 = util.EmptyOr(o.EnableHTTP2, lo.ToPtr(true))
}

func (o *HTTPS2HTTPSPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.RequestHeaders = o.RequestHeaders.Clone()
	out.EnableHTTP2 = util.ClonePtr(o.EnableHTTP2)
	out.AutoTLS = o.AutoTLS.Clone()
	return &out
}

type HTTP2HTTPPluginOptions struct {
	Type              string           `json:"type,omitempty"`
	LocalAddr         string           `json:"localAddr,omitempty"`
	HostHeaderRewrite string           `json:"hostHeaderRewrite,omitempty"`
	RequestHeaders    HeaderOperations `json:"requestHeaders,omitempty"`
}

func (o *HTTP2HTTPPluginOptions) Complete() {}

func (o *HTTP2HTTPPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.RequestHeaders = o.RequestHeaders.Clone()
	return &out
}

type Socks5PluginOptions struct {
	Type     string `json:"type,omitempty"`
	Username string `json:"username,omitempty"`
	Password string `json:"password,omitempty"`
}

func (o *Socks5PluginOptions) Complete() {}

func (o *Socks5PluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}

type StaticFilePluginOptions struct {
	Type         string `json:"type,omitempty"`
	LocalPath    string `json:"localPath,omitempty"`
	StripPrefix  string `json:"stripPrefix,omitempty"`
	HTTPUser     string `json:"httpUser,omitempty"`
	HTTPPassword string `json:"httpPassword,omitempty"`
}

func (o *StaticFilePluginOptions) Complete() {}

func (o *StaticFilePluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}

type UnixDomainSocketPluginOptions struct {
	Type     string `json:"type,omitempty"`
	UnixPath string `json:"unixPath,omitempty"`
}

func (o *UnixDomainSocketPluginOptions) Complete() {}

func (o *UnixDomainSocketPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}

type TLS2RawPluginOptions struct {
	Type      string          `json:"type,omitempty"`
	LocalAddr string          `json:"localAddr,omitempty"`
	CrtPath   string          `json:"crtPath,omitempty"`
	KeyPath   string          `json:"keyPath,omitempty"`
	AutoTLS   *AutoTLSOptions `json:"autoTLS,omitempty"`
}

func (o *TLS2RawPluginOptions) Complete() {}

func (o *TLS2RawPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	out.AutoTLS = o.AutoTLS.Clone()
	return &out
}

type VirtualNetPluginOptions struct {
	Type string `json:"type,omitempty"`
}

func (o *VirtualNetPluginOptions) Complete() {}

func (o *VirtualNetPluginOptions) Clone() ClientPluginOptions {
	if o == nil {
		return nil
	}
	out := *o
	return &out
}
