package provider

import (
	"context"
	"encoding/json"
	"strings"

	"github.com/deepmap/oapi-codegen/pkg/types"
	"github.com/google/uuid"
	"github.com/hashicorp/terraform-plugin-log/tflog"
	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/tidwall/gjson"

	nb "github.com/nautobot/go-nautobot"
)

func resourceManufacturer() *schema.Resource {
	return &schema.Resource{
		Description: "This object manages a manufacturer in Nautobot",

		CreateContext: resourceManufacturerCreate,
		ReadContext:   resourceManufacturerRead,
		UpdateContext: resourceManufacturerUpdate,
		DeleteContext: resourceManufacturerDelete,

		Schema: map[string]*schema.Schema{
			"created": {
				Description: "Manufacturer's creation date.",
				Type:        schema.TypeString,
				Computed:    true,
			},
			"description": {
				Description: "Manufacturer's description.",
				Type:        schema.TypeString,
				Optional:    true,
			},
			"custom_fields": {
				Description: "Manufacturer custom fields.",
				Type:        schema.TypeMap,
				Optional:    true,
			},
			"devicetype_count": {
				Description: "Manufacturer's device count.",
				Type:        schema.TypeInt,
				Computed:    true,
			},
			"display": {
				Description: "Manufacturer's display name.",
				Type:        schema.TypeString,
				Optional:    true,
				Computed:    true,
			},
			"id": {
				Description: "Manufacturer's UUID.",
				Type:        schema.TypeString,
				Computed:    true,
			},
			"inventoryitem_count": {
				Description: "Manufacturer's inventory item count.",
				Type:        schema.TypeInt,
				Computed:    true,
			},
			"last_updated": {
				Description: "Manufacturer's last update.",
				Type:        schema.TypeString,
				Computed:    true,
			},
			"name": {
				Description: "Manufacturer's name.",
				Type:        schema.TypeString,
				Required:    true,
			},
			"notes_url": {
				Description: "Notes for manufacturer.",
				Type:        schema.TypeString,
				Optional:    true,
				Computed:    true,
			},
			"platform_count": {
				Description: "Manufacturer's platform count.",
				Type:        schema.TypeInt,
				Computed:    true,
			},
			"slug": {
				Description: "Manufacturer's slug.",
				Type:        schema.TypeString,
				Optional:    true,
				Computed:    true,
			},
			"url": {
				Description: "Manufacturer's URL.",
				Type:        schema.TypeString,
				Optional:    true,
				Computed:    true,
			},
		},
	}
}

func resourceManufacturerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	c := meta.(*apiClient).Client
	s := meta.(*apiClient).Server

	var m nb.ManufacturerRequest

	name, ok := d.GetOk("name")
	n := name.(string)
	if ok {
		m.Name = n
	}

	m.Description = &n
	description, ok := d.GetOk("description")
	if ok {
		t := description.(string)
		m.Description = &t
	}

	sl := strings.ReplaceAll(strings.ToLower(n), " ", "-")
	m.Slug = &sl
	slug, ok := d.GetOk("slug")
	if ok {
		t := slug.(string)
		m.Slug = &t
	}

	rsp, err := c.DcimManufacturersCreateWithResponse(
		ctx,
		nb.DcimManufacturersCreateJSONRequestBody(m))
	if err != nil {
		return diag.Errorf("failed to create manufacturer %s on %s: %s", name.(string), s, err.Error())
	}
	data := string(rsp.Body)

	dataName := gjson.Get(data, "name.0")

	if dataName.String() == "manufacturer with this name already exists." {
		rsp, err := c.DcimManufacturersListWithResponse(
			ctx,
			&nb.DcimManufacturersListParams{
				NameIe: &[]string{n},
			})

		if err != nil {
			return diag.Errorf("failed to get manufacturer %s from %s: %s", n, s, err.Error())
		}
		id := gjson.Get(string(rsp.Body), "results.0.id")

		d.SetId(id.String())

		return resourceManufacturerRead(ctx, d, meta)
	}

	tflog.Trace(ctx, "manufacturer created", map[string]interface{}{
		"name": name.(string),
		"data": []interface{}{description, slug},
	})

	id := gjson.Get(data, "id")

	d.SetId(id.String())

	return resourceManufacturerRead(ctx, d, meta)
}

func resourceManufacturerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	c := meta.(*apiClient).Client
	id := d.Get("id").(string)
	rsp, err := c.DcimManufacturersListWithResponse(
		ctx,
		&nb.DcimManufacturersListParams{
			IdIe: &[]types.UUID{uuid.MustParse(id)},
		})

	var diags diag.Diagnostics
	name := d.Get("name").(string)
	s := meta.(*apiClient).Server
	if err != nil {
		return diag.Errorf("failed to get manufacturer %s from %s: %s", name, s, err.Error())
	}

	// If the Manufacturer is in the state file, but it is not in the Nautobot platform
	// the response we get from DcimManufacturersListWithResponse is: {"count":0,"next":null,"previous":null,"results":[]}
	// When you create something in Terraform but delete it manually, Terraform should gracefully handle it.
	// We should set the ID to an empty string so Terraform "destroys" the resource in state.
	count := gjson.Get(string(rsp.Body), "count")
	if count.String() == "0" {
		d.SetId("")
		return diags
	}

	results := gjson.Get(string(rsp.Body), "results.0")

	resultsReader := strings.NewReader(results.String())

	item := make(map[string]interface{})

	err = json.NewDecoder(resultsReader).Decode(&item)
	if err != nil {
		return diag.Errorf("failed to decode manufacturer %s from %s: %s", name, s, err.Error())
	}

	d.Set("name", item["name"].(string))
	d.Set("created", item["created"].(string))
	d.Set("description", item["description"].(string))
	d.Set("display", item["display"].(string))
	d.Set("id", item["id"].(string))
	d.Set("notes_url", item["notes_url"].(string))
	d.Set("slug", item["slug"].(string))
	d.Set("url", item["url"].(string))
	d.Set("last_updated", item["last_updated"].(string))

	switch v := item["devicetype_count"].(type) {
	case int:
		d.Set("devicetype_count", v)
	case float64:
		d.Set("devicetype_count", int(v))
	default:
	}
	switch v := item["inventoryitem_count"].(type) {
	case int:
		d.Set("inventoryitem_count", v)
	case float64:
		d.Set("inventoryitem_count", int(v))
	default:
	}
	switch v := item["platform_count"].(type) {
	case int:
		d.Set("platform_count", v)
	case float64:
		d.Set("platform_count", int(v))
	default:
	}

	return diags
}

func resourceManufacturerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	c := meta.(*apiClient).Client
	s := meta.(*apiClient).Server

	name := d.Get("name").(string)
	id := d.Get("id").(string)

	var m nb.PatchedManufacturerRequest

	if d.HasChange("name") {
		m.Name = &name
	}

	desc := d.Get("description").(string)
	if d.HasChange("description") {
		m.Description = &desc
	}

	slug := d.Get("slug").(string)
	if d.HasChange("slug") {
		m.Description = &slug
	}

	_, err := c.DcimManufacturersPartialUpdateWithResponse(
		ctx,
		uuid.MustParse(id),
		nb.DcimManufacturersPartialUpdateJSONRequestBody(m))
	if err != nil {
		return diag.Errorf("failed to update manufacturer %s on %s: %s", name, s, err.Error())
	}

	tflog.Trace(ctx, "manufacturer updated", map[string]interface{}{
		"name": name,
		"data": []string{desc, slug},
	})

	return resourceManufacturerRead(ctx, d, meta)
}

func resourceManufacturerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
	var diags diag.Diagnostics

	c := meta.(*apiClient).Client
	s := meta.(*apiClient).Server

	id := d.Get("id").(string)
	name := d.Get("name").(string)

	_, err := c.DcimManufacturersDestroy(
		ctx,
		uuid.MustParse(id))
	if err != nil {
		return diag.Errorf("failed to delete manufacturer %s on %s: %s", name, s, err.Error())
	}

	// d.SetId("") is automatically called assuming delete returns no errors, but
	// it is added here for explicitness.
	d.SetId("")

	return diags
}
