package provider

import (
	"context"
	"crypto/tls"
	"net/http"

	"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/provider"
	"github.com/hashicorp/terraform-plugin-framework/provider/schema"
	"github.com/hashicorp/terraform-plugin-framework/resource"
	"github.com/hashicorp/terraform-plugin-framework/schema/validator"
	"github.com/hashicorp/terraform-plugin-framework/types"
	log "github.com/sirupsen/logrus"
	decort "repository.basistech.ru/BASIS/decort-golang-sdk"
	sdk_config "repository.basistech.ru/BASIS/decort-golang-sdk/config"
)

// enumerated constants that define authentication modes for Configure
const (
	MODE_UNDEF = iota // this is the invalid mode - it should never be seen
	MODE_LEGACY
	MODE_DECS3O
	MODE_JWT
	MODE_BVS
)

// Ensure DynamixProvider satisfies various provider interfaces.
var _ provider.Provider = &DynamixProvider{}

// DynamixProvider defines the provider implementation.
type DynamixProvider struct {
	// version is set to the provider version on release, "dev" when the
	// provider is built and ran locally, and "test" when running acceptance
	// testing.
	version string
}

// dynamixProviderModel describes the provider data model.
type dynamixProviderModel struct {
	Authenticator      types.String `tfsdk:"authenticator"`
	Oauth2Url          types.String `tfsdk:"oauth2_url"`
	ControllerUrl      types.String `tfsdk:"controller_url"`
	User               types.String `tfsdk:"user"`
	Password           types.String `tfsdk:"password"`
	BvsUser            types.String `tfsdk:"bvs_user"`
	BvsPassword        types.String `tfsdk:"bvs_password"`
	Domain             types.String `tfsdk:"domain"`
	AppId              types.String `tfsdk:"app_id"`
	AppSecret          types.String `tfsdk:"app_secret"`
	Jwt                types.String `tfsdk:"jwt"`
	AllowUnverifiedSsl types.Bool   `tfsdk:"allow_unverified_ssl"`
	PathConfig         types.String `tfsdk:"path_config"`
	PathToken          types.String `tfsdk:"path_token"`
	TimeToRefresh      types.Int64  `tfsdk:"time_to_refresh"`
}

func (p *DynamixProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
	resp.TypeName = "dynamix"
	resp.Version = p.version
}

func (p *DynamixProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"authenticator": schema.StringAttribute{
				MarkdownDescription: "Authentication mode to use when connecting to DYNAMIX cloud API. Should be one of 'decs3o', 'legacy', 'jwt' or 'bvs'.",
				Required:            true,
				Validators: []validator.String{
					stringvalidator.OneOfCaseInsensitive("decs3o", "legacy", "jwt", "bvs"), // ignore case while validating
				},
			},
			"oauth2_url": schema.StringAttribute{
				MarkdownDescription: "OAuth2 application URL in 'decs3o' and 'bvs' authentication mode.",
				Optional:            true,
			},
			"controller_url": schema.StringAttribute{
				MarkdownDescription: "URL of DYNAMIX Cloud controller to use. API calls will be directed to this URL.",
				Required:            true,
			},
			"user": schema.StringAttribute{
				MarkdownDescription: "User name for DYNAMIX cloud API operations in 'legacy' authentication mode.",
				Optional:            true,
			},
			"password": schema.StringAttribute{
				MarkdownDescription: "User password for DYNAMIX cloud API operations in 'legacy' authentication mode.",
				Optional:            true,
			},
			"bvs_user": schema.StringAttribute{
				MarkdownDescription: "User name for DYNAMIX cloud API operations in 'bvs' authentication mode.",
				Optional:            true,
			},
			"bvs_password": schema.StringAttribute{
				MarkdownDescription: "User password for DYNAMIX cloud API operations in 'bvs' authentication mode.",
				Optional:            true,
			},
			"domain": schema.StringAttribute{
				MarkdownDescription: "User password for DYNAMIX cloud API operations in 'bvs' authentication mode.",
				Optional:            true,
			},
			"app_id": schema.StringAttribute{
				MarkdownDescription: "Application ID to access DYNAMIX cloud API in 'decs3o' and 'bvs' authentication mode.",
				Optional:            true,
			},
			"app_secret": schema.StringAttribute{
				MarkdownDescription: "Application secret to access DYNAMIX cloud API in 'decs3o' and 'bvs' authentication mode.",
				Optional:            true,
			},
			"jwt": schema.StringAttribute{
				MarkdownDescription: "JWT to access DYNAMIX cloud API in 'jwt' authentication mode.",
				Optional:            true,
			},
			"allow_unverified_ssl": schema.BoolAttribute{
				MarkdownDescription: "If true, DYNAMIX API will not verify SSL certificates. Use this with caution and in trusted environments only! Default is false.",
				Optional:            true,
			},
			"path_config": schema.StringAttribute{
				MarkdownDescription: "The path of the configuration file entry.",
				Optional:            true,
			},
			"path_token": schema.StringAttribute{
				MarkdownDescription: "The path of the token file entry.",
				Optional:            true,
			},
			"time_to_refresh": schema.Int64Attribute{
				MarkdownDescription: "The number of minutes before the expiration of the token, a refresh will be made.",
				Optional:            true,
			},
		},
	}
}

func (p *DynamixProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
	// Retrieve provider data from configuration
	var config dynamixProviderModel
	diags := req.Config.Get(ctx, &config)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		resp.Diagnostics.AddError("Provider Configure: ", "error after req.Config.Get")
		log.Error("Provider Configure error after req.Config.Get")
		return
	}

	// Set up default values, values from env and save user provided values into dynamixConfig
	dynamixConfig := dynamixProviderConfig{}
	dynamixConfig.new(config)

	// Validate and set up authentication mode
	mode, err := dynamixConfig.validateAuthenticator()
	if err != nil {
		log.Error(err)
		resp.Diagnostics.AddError("Provider Configure: validate error:", err.Error())
		return
	}

	// Set up client transport
	if dynamixConfig.allowUnverifiedSsl {
		log.Warn("Provider Configure: allow_unverified_ssl is set - will not check certificates!")
		transCfg := &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} //nolint:gosec
		dynamixConfig.cc_client = &http.Client{
			Transport: transCfg,
		}
	} else {
		dynamixConfig.cc_client = &http.Client{}
	}

	// Set up clients for data sources and resources depending on authentication mode
	switch mode {
	case MODE_LEGACY:
		legacyConf := sdk_config.LegacyConfig{
			Username:      dynamixConfig.user,
			Password:      dynamixConfig.password,
			DecortURL:     dynamixConfig.controllerUrl,
			SSLSkipVerify: dynamixConfig.allowUnverifiedSsl,
		}
		legacyClient := decort.NewLegacy(legacyConf)
		resp.DataSourceData = legacyClient
		resp.ResourceData = legacyClient
	case MODE_JWT:
	case MODE_DECS3O:
		sdkConf := sdk_config.Config{
			AppID:         dynamixConfig.appId,
			AppSecret:     dynamixConfig.appSecret,
			SSOURL:        dynamixConfig.oauth2Url,
			DecortURL:     dynamixConfig.controllerUrl,
			SSLSkipVerify: dynamixConfig.allowUnverifiedSsl,
		}
		decortClient := decort.New(sdkConf)
		resp.DataSourceData = decortClient
		resp.ResourceData = decortClient
	case MODE_BVS:
		bvsConf := sdk_config.BVSConfig{
			AppID:         dynamixConfig.appId,
			AppSecret:     dynamixConfig.appSecret,
			SSOURL:        dynamixConfig.oauth2Url,
			DecortURL:     dynamixConfig.controllerUrl,
			SSLSkipVerify: dynamixConfig.allowUnverifiedSsl,
			Username:      dynamixConfig.bvsUser,
			Password:      dynamixConfig.bvsPassword,
			Domain:        dynamixConfig.domain,
			Token:         dynamixConfig.token,
			PathCfg:       dynamixConfig.pathConfig,
			PathToken:     dynamixConfig.pathToken,
			TimeToRefresh: dynamixConfig.timeToRefresh,
		}
		bvsClient := decort.NewBVS(bvsConf)
		resp.DataSourceData = bvsClient
		resp.ResourceData = bvsClient
	default:
		log.Debugf("unknown authenticator mode code %d provided", mode)
		return
	}
}

func (p *DynamixProvider) Resources(_ context.Context) []func() resource.Resource {
	return newResourcesMap()
}

func (p *DynamixProvider) DataSources(_ context.Context) []func() datasource.DataSource {
	return newDataSourcesMap()
}

func New(version string) func() provider.Provider {
	return func() provider.Provider {
		return &DynamixProvider{
			version: version,
		}
	}
}
