package provider

import (
	"context"
	"os"

	"github.com/hashicorp-demoapp/hashicups-client-go"
	"github.com/hashicorp/terraform-plugin-framework/datasource"
	"github.com/hashicorp/terraform-plugin-framework/function"
	"github.com/hashicorp/terraform-plugin-framework/path"
	"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/types"
	"github.com/hashicorp/terraform-plugin-log/tflog"
)

// Ensure the implementation satisfies the expected interfaces.
var (
	_ provider.Provider              = &hashicupsProvider{}
	_ provider.ProviderWithFunctions = &hashicupsProvider{}
)

// New is a helper function to simplify provider server and testing implementation.
func New(version string) func() provider.Provider {
	return func() provider.Provider {
		return &hashicupsProvider{
			version: version,
		}
	}
}

// hashicupsProvider is the provider implementation.
type hashicupsProvider 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
}

// The Terraform Plugin Framework uses Go struct types with tfsdk struct field tags
// to map schema definitions into Go types with the actual data.
// The types within the struct must align with the types in the schema.
type hashicupsProviderModel struct {
	Host     types.String `tfsdk:"host"`
	Username types.String `tfsdk:"username"`
	Password types.String `tfsdk:"password"`
}

// Metadata returns the provider type name.
func (p *hashicupsProvider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) {
	resp.TypeName = "hashicups"
	resp.Version = p.version
}

// Schema defines the provider-level schema for configuration data.
func (p *hashicupsProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) {
	resp.Schema = schema.Schema{
		Attributes: map[string]schema.Attribute{
			"host": schema.StringAttribute{
				Optional: true,
			},
			"username": schema.StringAttribute{
				Optional: true,
			},
			"password": schema.StringAttribute{
				Optional:  true,
				Sensitive: true,
			},
		},
	}
}

func (p *hashicupsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
	tflog.Info(ctx, "Configuring HashiCups client")

	// 1. Retrieves values from the configuration.
	// The method will attempt to retrieve values from the provider configuration and convert it to an providerModel struct.
	var config hashicupsProviderModel
	diags := req.Config.Get(ctx, &config)
	resp.Diagnostics.Append(diags...)
	if resp.Diagnostics.HasError() {
		return
	}

	// 2. Checks for unknown configuration values.
	// The method prevents an unexpectedly misconfigured client, if Terraform configuration values are only known after another resource is applied.
	//
	// If practitioner provided a configuration value for any of the attributes, it must be a known value.
	// 2.1 host
	if config.Host.IsUnknown() {
		resp.Diagnostics.AddAttributeError(
			path.Root("host"),
			"Unknown HashiCups API Host",
			"The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API host. "+
				"Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_HOST environment variable.",
		)
	}

	// 2.2 username
	if config.Username.IsUnknown() {
		resp.Diagnostics.AddAttributeError(
			path.Root("username"),
			"Unknown HashiCups API Username",
			"The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API username. "+
				"Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_USERNAME environment variable.",
		)
	}

	// 2.3 password
	if config.Password.IsUnknown() {
		resp.Diagnostics.AddAttributeError(
			path.Root("password"),
			"Unknown HashiCups API Password",
			"The provider cannot create the HashiCups API client as there is an unknown configuration value for the HashiCups API password. "+
				"Either target apply the source of the value first, set the value statically in the configuration, or use the HASHICUPS_PASSWORD environment variable.",
		)
	}

	// 2.4 check results
	if resp.Diagnostics.HasError() {
		return
	}

	// 3. Retrieves values from environment variables.
	// The method retrieves values from environment variables, then overrides them with any set Terraform configuration values.

	// 3.1 Default values to environment variables.
	host := os.Getenv("HASHICUPS_HOST")         // See docker-compose/docker-compose.yml:  http://localhost:19090
	username := os.Getenv("HASHICUPS_USERNAME") // We used: curl -X POST localhost:19090/signup -d '{"username":"education", "password":"test123"}'
	password := os.Getenv("HASHICUPS_PASSWORD") // We used: curl -X POST localhost:19090/signup -d '{"username":"education", "password":"test123"}'

	// 3.2 but override with Terraform configuration value if set.
	if !config.Host.IsNull() {
		host = config.Host.ValueString()
	}

	if !config.Username.IsNull() {
		username = config.Username.ValueString()
	}

	if !config.Password.IsNull() {
		password = config.Password.ValueString()
	}

	// 3.3 verify values are set
	// If any of the expected configurations are missing, return
	// errors with provider-specific guidance.

	if host == "" {
		resp.Diagnostics.AddAttributeError(
			path.Root("host"),
			"Missing HashiCups API Host",
			"The provider cannot create the HashiCups API client as there is a missing or empty value for the HashiCups API host. "+
				"Set the host value in the configuration or use the HASHICUPS_HOST environment variable. "+
				"If either is already set, ensure the value is not empty.",
		)
	}

	if username == "" {
		resp.Diagnostics.AddAttributeError(
			path.Root("username"),
			"Missing HashiCups API Username",
			"The provider cannot create the HashiCups API client as there is a missing or empty value for the HashiCups API username. "+
				"Set the username value in the configuration or use the HASHICUPS_USERNAME environment variable. "+
				"If either is already set, ensure the value is not empty.",
		)
	}

	if password == "" {
		resp.Diagnostics.AddAttributeError(
			path.Root("password"),
			"Missing HashiCups API Password",
			"The provider cannot create the HashiCups API client as there is a missing or empty value for the HashiCups API password. "+
				"Set the password value in the configuration or use the HASHICUPS_PASSWORD environment variable. "+
				"If either is already set, ensure the value is not empty.",
		)
	}
	// 3.4 check results
	if resp.Diagnostics.HasError() {
		return
	}

	// All fields added to context will be included in subsequent log messages.
	ctx = tflog.SetField(ctx, "hashicups_host", host)
	ctx = tflog.SetField(ctx, "hashicups_username", username)
	ctx = tflog.SetField(ctx, "hashicups_password", password)
	ctx = tflog.MaskFieldValuesWithFieldKeys(ctx, "hashicups_password")

	tflog.Debug(ctx, "Creating HashiCups API Client")

	// 4. Creates API client.
	// The method invokes the HashiCups API client's NewClient function.
	//
	// Create a new HashiCups client using the configuration values
	client, err := hashicups.NewClient(&host, &username, &password)
	if err != nil {
		resp.Diagnostics.AddError(
			"Unable to Create HashiCups API Client",
			"An unexpected error occurred when creating the HashiCups API client. "+
				"If the error is not clear, please contact the provider developers.\n\n"+
				"HashiCups Client Error: "+err.Error(),
		)
		return
	}

	// 5. Stores configured client for data source and resource usage.
	// The method sets the DataSourceData and ResourceData fields of the response,
	// so the client is available for usage by data source and resource implementations.
	//
	// Make the HashiCups client available during DataSource and Resource type Configure methods.
	resp.DataSourceData = client
	resp.ResourceData = client

	// Fields can also be added to only a single log message.
	tflog.Info(ctx, "Successfully created HashiCups API Client", map[string]any{
		"success": true,
	})
}

// DataSources defines the data sources implemented in the provider.
func (p *hashicupsProvider) DataSources(_ context.Context) []func() datasource.DataSource {
	return []func() datasource.DataSource{
		NewCoffeesDataSource,
	}
}

// Resources defines the resources implemented in the provider.
func (p *hashicupsProvider) Resources(_ context.Context) []func() resource.Resource {
	return []func() resource.Resource{
		NewOrderResource,
	}
}

func (p *hashicupsProvider) Functions(ctx context.Context) []func() function.Function {
	return []func() function.Function{
		NewComputeTaxFunction,
	}
}

//// Ensure hashicupsProvider satisfies various provider interfaces.
//var _ provider.Provider = &hashicupsProvider{}
//var _ provider.ProviderWithFunctions = &hashicupsProvider{}
//
//// hashicupsProvider defines the provider implementation.
//type hashicupsProvider 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
//}
//
//// ScaffoldingProviderModel describes the provider data model.
//type ScaffoldingProviderModel struct {
//	Endpoint types.String `tfsdk:"endpoint"`
//}
//
//func (p *hashicupsProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) {
//	resp.TypeName = "scaffolding"
//	resp.Version = p.version
//}
//
//func (p *hashicupsProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) {
//	resp.Schema = schema.Schema{
//		Attributes: map[string]schema.Attribute{
//			"endpoint": schema.StringAttribute{
//				MarkdownDescription: "Example provider attribute",
//				Optional:            true,
//			},
//		},
//	}
//}
//
//func (p *hashicupsProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) {
//	var data ScaffoldingProviderModel
//
//	resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
//
//	if resp.Diagnostics.HasError() {
//		return
//	}
//
//	// Configuration values are now available.
//	// if data.Endpoint.IsNull() { /* ... */ }
//
//	// Example client configuration for data sources and resources
//	client := http.DefaultClient
//	resp.DataSourceData = client
//	resp.ResourceData = client
//}
//
//func (p *hashicupsProvider) Resources(ctx context.Context) []func() resource.Resource {
//	return []func() resource.Resource{
//		NewExampleResource,
//	}
//}
//
//func (p *hashicupsProvider) DataSources(ctx context.Context) []func() datasource.DataSource {
//	return []func() datasource.DataSource{
//		NewExampleDataSource,
//	}
//}
//
//func (p *hashicupsProvider) Functions(ctx context.Context) []func() function.Function {
//	return []func() function.Function{
//		NewExampleFunction,
//	}
//}
//
//func New(version string) func() provider.Provider {
//	return func() provider.Provider {
//		return &hashicupsProvider{
//			version: version,
//		}
//	}
//}