package exoscale

import (
	"context"
	"errors"

	"github.com/hashicorp/terraform-plugin-log/tflog"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

	egoscale "github.com/exoscale/egoscale/v2"
	exoapi "github.com/exoscale/egoscale/v2/api"
	"github.com/exoscale/terraform-provider-exoscale/pkg/config"
	"github.com/exoscale/terraform-provider-exoscale/pkg/general"
)

const (
	resSSHKeyAttrFingerprint = "fingerprint"
	resSSHKeyAttrName        = "name"
	resSSHKeyAttrPublicKey   = "public_key"
)

func resourceSSHKeyIDString(d general.ResourceIDStringer) string {
	return general.ResourceIDString(d, "exoscale_ssh_key")
}

func resourceSSHKey() *schema.Resource {
	return &schema.Resource{
		Schema: map[string]*schema.Schema{
			resSSHKeyAttrFingerprint: {
				Type:        schema.TypeString,
				Computed:    true,
				Description: "The SSH key unique identifier.",
			},
			resSSHKeyAttrName: {
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "The SSH key name.",
			},
			resSSHKeyAttrPublicKey: {
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "The SSH *public* key that will be authorized in compute instances.",
			},
		},

		CreateContext: resourceSSHKeyCreate,
		ReadContext:   resourceSSHKeyRead,
		DeleteContext: resourceSSHKeyDelete,

		Importer: &schema.ResourceImporter{
			StateContext: schema.ImportStatePassthroughContext,
		},

		Timeouts: &schema.ResourceTimeout{
			Create: schema.DefaultTimeout(config.DefaultTimeout),
			Read:   schema.DefaultTimeout(config.DefaultTimeout),
			Delete: schema.DefaultTimeout(config.DefaultTimeout),
		},
	}
}

func resourceSSHKeyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	tflog.Debug(ctx, "beginning create", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	zone := defaultZone

	ctx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutCreate))
	ctx = exoapi.WithEndpoint(ctx, exoapi.NewReqEndpoint(getEnvironment(meta), zone))
	defer cancel()

	client := getClient(meta)

	sshKey, err := client.RegisterSSHKey(
		ctx,
		zone,
		d.Get(resSSHKeyAttrName).(string),
		d.Get(resSSHKeyAttrPublicKey).(string),
	)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId(*sshKey.Name)

	tflog.Debug(ctx, "create finished successfully", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	return resourceSSHKeyRead(ctx, d, meta)
}

func resourceSSHKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	tflog.Debug(ctx, "beginning read", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	zone := defaultZone

	ctx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutRead))
	ctx = exoapi.WithEndpoint(ctx, exoapi.NewReqEndpoint(getEnvironment(meta), zone))
	defer cancel()

	client := getClient(meta)

	sshKey, err := client.GetSSHKey(ctx, zone, d.Id())
	if err != nil {
		if errors.Is(err, exoapi.ErrNotFound) {
			// Resource doesn't exist anymore, signaling the core to remove it from the state.
			d.SetId("")
			return nil
		}
		return diag.FromErr(err)
	}

	tflog.Debug(ctx, "read finished successfully", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	return diag.FromErr(resourceSSHKeyApply(ctx, d, sshKey))
}

func resourceSSHKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	tflog.Debug(ctx, "beginning delete", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	zone := defaultZone

	ctx, cancel := context.WithTimeout(ctx, d.Timeout(schema.TimeoutDelete))
	ctx = exoapi.WithEndpoint(ctx, exoapi.NewReqEndpoint(getEnvironment(meta), zone))
	defer cancel()

	client := getClient(meta)

	if err := client.DeleteSSHKey(ctx, zone, &egoscale.SSHKey{Name: nonEmptyStringPtr(d.Id())}); err != nil {
		return diag.FromErr(err)
	}

	tflog.Debug(ctx, "delete finished successfully", map[string]interface{}{
		"id": resourceSSHKeyIDString(d),
	})

	return nil
}

func resourceSSHKeyApply(_ context.Context, d *schema.ResourceData, sshKey *egoscale.SSHKey) error {
	if err := d.Set(resSSHKeyAttrName, *sshKey.Name); err != nil {
		return err
	}

	if err := d.Set(resSSHKeyAttrFingerprint, *sshKey.Fingerprint); err != nil {
		return err
	}

	return nil
}
