/*
Copyright (c) 2019-2022 Digital Energy Cloud Solutions LLC. All Rights Reserved.
Authors:
Petr Krutov, <petr.krutov@digitalenergy.online>
Stanislav Solovev, <spsolovev@digitalenergy.online>
Kasim Baybikov, <kmbaybikov@basistech.ru>

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.
*/

/*
Terraform DECORT provider - manage resources provided by DECORT (Digital Energy Cloud
Orchestration Technology) with Terraform by Hashicorp.

Source code: https://repository.basistech.ru/BASIS/terraform-provider-decort

Please see README.md to learn where to place source code so that it
builds seamlessly.

Documentation: https://repository.basistech.ru/BASIS/terraform-provider-decort/wiki
*/

package bservice

import (
	"context"
	"strconv"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/bservice"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/validators"
)

func resourceBasicServiceGroupCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceBasicServiceGroupCreate")

	c := m.(*controller.ControllerCfg)
	req := bservice.GroupAddRequest{
		ServiceID:       uint64(d.Get("service_id").(int)),
		Name:            d.Get("compgroup_name").(string),
		Count:           uint64(d.Get("comp_count").(int)),
		CPU:             uint64(d.Get("cpu").(int)),
		RAM:             uint64(d.Get("ram").(int)),
		Disk:            uint64(d.Get("disk").(int)),
		ImageID:         uint64(d.Get("image_id").(int)),
		StoragePolicyID: uint64(d.Get("storage_policy_id").(int)),
	}

	if role, ok := d.GetOk("role"); ok {
		req.Role = role.(string)
	}

	if timeoutStart, ok := d.GetOk("timeout_start"); ok {
		req.TimeoutStart = uint64(timeoutStart.(int))
	}

	///4.4.0
	if sepId, ok := d.GetOk("sep_id"); ok {
		req.SEPID = uint64(sepId.(int))
	}

	if sepPool, ok := d.GetOk("sep_pool"); ok {
		req.SEPPool = sepPool.(string)
	}

	if cloudInit, ok := d.GetOk("cloud_init"); ok {
		req.UserData = cloudInit.(string)
	}
	///

	if vinses, ok := d.GetOk("vinses"); ok {
		res := []uint64{}
		for _, vins := range vinses.([]interface{}) {
			res = append(res, uint64(vins.(int)))
		}

		req.VINSes = res
	}
	if extnets, ok := d.GetOk("extnets"); ok {
		res := []uint64{}
		for _, enet := range extnets.([]interface{}) {
			res = append(res, uint64(enet.(int)))
		}

		req.ExtNets = res
	}

	if chipset, ok := d.GetOk("chipset"); ok {
		req.Chipset = chipset.(string)
	}

	compgroupId, err := c.CloudAPI().BService().GroupAdd(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId(strconv.FormatUint(compgroupId, 10))
	d.Set("compgroup_id", compgroupId)

	serviceId := uint64(d.Get("service_id").(int))

	if d.Get("start").(bool) {
		log.Debugf("trying to start bservice group %v", compgroupId)
		_, err := c.CloudAPI().BService().GroupStart(ctx, bservice.GroupStartRequest{
			ServiceID:   serviceId,
			CompGroupID: compgroupId,
		})

		if err != nil {
			return diag.FromErr(err)
		}
	}

	return resourceBasicServiceGroupRead(ctx, d, m)
}

func resourceBasicServiceGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceBasicServiceGroupRead")

	bsg, err := utilityBasicServiceGroupCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	flattenResourceBasicServiceGroup(d, bsg)

	return nil
}

func resourceBasicServiceGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceBasicServiceGroupDelete")

	bsg, err := utilityBasicServiceGroupCheckPresence(ctx, d, m)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	c := m.(*controller.ControllerCfg)
	req := bservice.GroupRemoveRequest{
		ServiceID:   bsg.ServiceID,
		CompGroupID: bsg.ID,
	}

	_, err = c.CloudAPI().BService().GroupRemove(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}
	d.SetId("")

	return nil
}

func resourceBasicServiceGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceBasicServiceGroupEdit")
	c := m.(*controller.ControllerCfg)

	if d.HasChange("comp_count") {
		req := bservice.GroupResizeRequest{
			ServiceID:   uint64(d.Get("service_id").(int)),
			CompGroupID: uint64(d.Get("compgroup_id").(int)),
			Count:       int64(d.Get("comp_count").(int)),
			Chipset:     d.Get("chipset").(string),
			Mode:        d.Get("mode").(string),
		}

		_, err := c.CloudAPI().BService().GroupResize(ctx, req)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("start") {
		start := d.Get("start").(bool)
		if start {
			req := bservice.GroupStartRequest{
				ServiceID:   uint64(d.Get("service_id").(int)),
				CompGroupID: uint64(d.Get("compgroup_id").(int)),
			}

			_, err := c.CloudAPI().BService().GroupStart(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		} else {
			req := bservice.GroupStopRequest{
				ServiceID:   uint64(d.Get("service_id").(int)),
				CompGroupID: uint64(d.Get("compgroup_id").(int)),
				Force:       d.Get("force_stop").(bool),
			}

			_, err := c.CloudAPI().BService().GroupStop(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChanges("compgroup_name", "ram", "cpu", "disk", "role") {
		req := bservice.GroupUpdateRequest{
			ServiceID:   uint64(d.Get("service_id").(int)),
			CompGroupID: uint64(d.Get("compgroup_id").(int)),
			Name:        d.Get("compgroup_name").(string),
			Role:        d.Get("role").(string),
			CPU:         uint64(d.Get("cpu").(int)),
			RAM:         uint64(d.Get("ram").(int)),
			Disk:        uint64(d.Get("disk").(int)),
			Force:       d.Get("force_update").(bool),
		}

		_, err := c.CloudAPI().BService().GroupUpdate(ctx, req)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("extnets") {
		extnets := d.Get("extnets").([]interface{})

		res := []uint64{}
		for _, enet := range extnets {
			res = append(res, uint64(enet.(int)))
		}

		req := bservice.GroupUpdateExtNetRequest{
			ServiceID:   uint64(d.Get("service_id").(int)),
			CompGroupID: uint64(d.Get("compgroup_id").(int)),
			ExtNets:     res,
		}

		_, err := c.CloudAPI().BService().GroupUpdateExtNet(ctx, req)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("vinses") {
		vinses := d.Get("vinses").([]interface{})

		res := []uint64{}
		for _, vins := range vinses {
			res = append(res, uint64(vins.(int)))
		}

		req := bservice.GroupUpdateVINSRequest{
			ServiceID:   uint64(d.Get("service_id").(int)),
			CompGroupID: uint64(d.Get("compgroup_id").(int)),
			VINSes:      res,
		}

		_, err := c.CloudAPI().BService().GroupUpdateVINS(ctx, req)
		if err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("parents") {
		deletedParents := make([]interface{}, 0)
		addedParents := make([]interface{}, 0)

		old, new := d.GetChange("parents")
		oldConv := old.([]interface{})
		newConv := new.([]interface{})
		for _, el := range oldConv {
			if !isContainsParent(newConv, el) {
				deletedParents = append(deletedParents, el)
			}
		}
		for _, el := range newConv {
			if !isContainsParent(oldConv, el) {
				addedParents = append(addedParents, el)
			}
		}

		if len(deletedParents) > 0 {
			for _, parent := range deletedParents {
				parentConv := parent.(int)

				req := bservice.GroupParentRemoveRequest{
					ServiceID:   uint64(d.Get("service_id").(int)),
					CompGroupID: uint64(d.Get("compgroup_id").(int)),
					ParentID:    uint64(parentConv),
				}

				_, err := c.CloudAPI().BService().GroupParentRemove(ctx, req)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}

		if len(addedParents) > 0 {
			for _, parent := range addedParents {
				parentConv := parent.(int)

				req := bservice.GroupParentAddRequest{
					ServiceID:   uint64(d.Get("service_id").(int)),
					CompGroupID: uint64(d.Get("compgroup_id").(int)),
					ParentID:    uint64(parentConv),
				}

				_, err := c.CloudAPI().BService().GroupParentAdd(ctx, req)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}
	}

	if d.HasChange("remove_computes") {
		rcs := d.Get("remove_computes").([]interface{})
		if len(rcs) > 0 {
			for _, rc := range rcs {
				req := bservice.GroupComputeRemoveRequest{
					ServiceID:   uint64(d.Get("service_id").(int)),
					CompGroupID: uint64(d.Get("compgroup_id").(int)),
					ComputeID:   uint64(rc.(int)),
				}

				_, err := c.CloudAPI().BService().GroupComputeRemove(ctx, req)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}
	}

	return resourceBasicServiceGroupRead(ctx, d, m)
}

func isContainsParent(els []interface{}, el interface{}) bool {
	for _, elOld := range els {
		elOldConv := elOld.(int)
		elConv := el.(int)
		if elOldConv == elConv {
			return true
		}
	}
	return false
}

func resourceBasicServiceGroupSchemaMake() map[string]*schema.Schema {
	return map[string]*schema.Schema{
		"service_id": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "ID of the Basic Service to add a group to",
		},
		"compgroup_name": {
			Type:        schema.TypeString,
			Required:    true,
			Description: "name of the Compute Group to add",
		},
		"comp_count": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "computes number. Defines how many computes must be there in the group",
		},
		"cpu": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "compute CPU number. All computes in the group have the same CPU count",
		},
		"ram": {
			Type:     schema.TypeInt,
			Required: true,
			ValidateFunc: validation.All(
				validation.IntAtLeast(constants.MIN_RAM_PER_COMPUTE),
				validators.DivisibleBy(constants.RAM_DIVISIBILITY),
			),
			Description: "compute RAM volume in MB. All computes in the group have the same RAM volume",
		},
		"disk": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "compute boot disk size in GB",
		},
		"image_id": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "OS image ID to create computes from",
		},
		"storage_policy_id": {
			Type:        schema.TypeInt,
			Required:    true,
			Description: "storage policy id of compute. The rules of the specified storage policy will be used.",
		},
		///4.4.0
		"sep_id": {
			Type:        schema.TypeInt,
			Optional:    true,
			Computed:    true,
			Description: "storage endpoint provider ID",
		},
		"sep_pool": {
			Type:        schema.TypeString,
			Optional:    true,
			Computed:    true,
			Description: "pool to use if sepId is set, can be also empty if needed to be chosen by system",
		},
		"cloud_init": {
			Type:        schema.TypeString,
			Optional:    true,
			Description: "Optional cloud_init parameters. Applied when creating new compute instance only, ignored in all other cases.",
		},
		"chipset": {
			Type:         schema.TypeString,
			Optional:     true,
			ValidateFunc: validation.StringInSlice([]string{"i440fx", "Q35"}, false),
			Default:      "Q35",
			Description:  "Chipset for virtual machines.",
		},
		///
		"role": {
			Type:        schema.TypeString,
			Optional:    true,
			Computed:    true,
			Description: "group role tag. Can be empty string, does not have to be unique",
		},
		"timeout_start": {
			Type:        schema.TypeInt,
			Optional:    true,
			Computed:    true,
			Description: "time of Compute Group readiness",
		},
		"extnets": {
			Type:     schema.TypeList,
			Optional: true,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
			Description: "list of external networks to connect computes to",
		},
		"vinses": {
			Type:     schema.TypeList,
			Optional: true,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
			Description: "list of ViNSes to connect computes to",
		},
		"mode": {
			Type:         schema.TypeString,
			Optional:     true,
			Default:      "RELATIVE",
			ValidateFunc: validation.StringInSlice([]string{"RELATIVE", "ABSOLUTE"}, false),
			Description:  "(RELATIVE;ABSOLUTE) either delta or absolute value of computes",
		},
		"start": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     false,
			Description: "Start the specified Compute Group within BasicService",
		},
		"force_stop": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     false,
			Description: "force stop Compute Group",
		},
		"force_update": {
			Type:        schema.TypeBool,
			Optional:    true,
			Default:     false,
			Description: "force resize Compute Group",
		},
		"parents": {
			Type:     schema.TypeList,
			Optional: true,
			Computed: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
		},
		"remove_computes": {
			Type:     schema.TypeList,
			Optional: true,
			Elem: &schema.Schema{
				Type: schema.TypeInt,
			},
		},
		"compgroup_id": {
			Type:     schema.TypeInt,
			Optional: true,
			Computed: true,
		},
		"account_id": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"account_name": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"computes": {
			Type:     schema.TypeList,
			Computed: true,
			Elem: &schema.Resource{
				Schema: map[string]*schema.Schema{
					"chipset": {
						Type:     schema.TypeString,
						Computed: true,
					},
					"id": {
						Type:     schema.TypeInt,
						Computed: true,
					},
					"ip_addresses": {
						Type:     schema.TypeList,
						Computed: true,
						Elem: &schema.Schema{
							Type: schema.TypeString,
						},
					},
					"name": {
						Type:     schema.TypeString,
						Computed: true,
					},
					"os_users": {
						Type:     schema.TypeList,
						Computed: true,
						Elem: &schema.Resource{
							Schema: map[string]*schema.Schema{
								"login": {
									Type:     schema.TypeString,
									Computed: true,
								},
								"password": {
									Type:     schema.TypeString,
									Computed: true,
								},
							},
						},
					},
				},
			},
		},
		"consistency": {
			Type:     schema.TypeBool,
			Computed: true,
		},
		"created_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"created_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"deleted_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"deleted_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"driver": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"gid": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"guid": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"milestones": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"rg_id": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"rg_name": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"seq_no": {
			Type:     schema.TypeInt,
			Computed: true,
		},
		"status": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"tech_status": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"updated_by": {
			Type:     schema.TypeString,
			Computed: true,
		},
		"updated_time": {
			Type:     schema.TypeInt,
			Computed: true,
		},
	}
}

func ResourceBasicServiceGroup() *schema.Resource {
	return &schema.Resource{
		SchemaVersion: 1,

		CreateContext: resourceBasicServiceGroupCreate,
		ReadContext:   resourceBasicServiceGroupRead,
		UpdateContext: resourceBasicServiceGroupUpdate,
		DeleteContext: resourceBasicServiceGroupDelete,

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

		Timeouts: &schema.ResourceTimeout{
			Create:  &constants.Timeout600s,
			Read:    &constants.Timeout300s,
			Update:  &constants.Timeout300s,
			Delete:  &constants.Timeout300s,
			Default: &constants.Timeout300s,
		},

		Schema: resourceBasicServiceGroupSchemaMake(),
	}
}
