/*
Copyright (c) 2019-2023 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 lb

import (
	"context"
	"strconv"

	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/dc"

	"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
	log "github.com/sirupsen/logrus"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/lb"
	"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/status"
)

func resourceLBCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLBCreate called with name: %s", d.Get("name").(string))
	c := m.(*controller.ControllerCfg)

	if diags := checkParamsExistence(ctx, d, c); diags != nil {
		return diags
	}

	req := lb.CreateRequest{
		Name:     d.Get("name").(string),
		RGID:     uint64(d.Get("rg_id").(int)),
		ExtNetID: int64(d.Get("extnet_id").(int)),
		VINSID:   uint64(d.Get("vins_id").(int)),
	}
	if start, ok := d.GetOk("start"); ok {
		req.Start = start.(bool)
	}
	if desc, ok := d.GetOk("desc"); ok {
		req.Description = desc.(string)
	}
	if zoneID, ok := d.GetOk("zone_id"); ok {
		req.ZoneID = uint64(zoneID.(int))
	}
	if haMode, ok := d.GetOk("ha_mode"); ok {
		req.HighlyAvailable = haMode.(bool)
	}
	if sysctlParams, ok := d.GetOk("sysctl_params"); ok {
		syscrlSliceMaps := sysctlParams.([]interface{})
		res := make([]map[string]interface{}, 0, len(syscrlSliceMaps))
		for _, syscrlMap := range syscrlSliceMaps {
			tempMap := make(map[string]interface{})
			for k, v := range syscrlMap.(map[string]interface{}) {
				if intVal, err := strconv.Atoi(v.(string)); err == nil {
					tempMap[k] = intVal
					continue
				}
				tempMap[k] = v.(string)
			}
			res = append(res, tempMap)
		}
		req.SysctlParams = res
	}

	lbId, err := c.CloudBroker().LB().Create(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	d.SetId(strconv.FormatUint(lbId, 10))
	d.Set("lb_id", lbId)

	var warnings dc.Warnings

	if enable, ok := d.GetOk("enable"); ok {
		if enable.(bool) {
			if err := resourceLbEnable(ctx, lbId, m); err != nil {
				warnings.Add(err)
			}
		} else {
			if err := resourceLbDisable(ctx, lbId, m); err != nil {
				warnings.Add(err)
			}
		}
	}

	return append(warnings.Get(), resourceLBRead(ctx, d, m)...)
}

func resourceLBRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLBRead called for lb_id %s", d.Id())

	// c := m.(*controller.ControllerCfg)

	lbRec, err := utilityLBCheckPresence(ctx, d, m)
	if lbRec == nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	hasChanged := false

	switch lbRec.Status {
	case status.Modeled:
		return diag.Errorf("The LB is in status: %s, please, contact support for more information", lbRec.Status)
	case status.Creating:
	case status.Created:
	case status.Deleting:
	case status.Deleted:
		// lbId, _ := strconv.ParseUint(d.Id(), 10, 64)
		// restoreReq := lb.RestoreRequest{LBID: lbId}

		// _, err := c.CloudBroker().LB().Restore(ctx, restoreReq)
		// if err != nil {
		// 	return diag.FromErr(err)
		// }

		// if enable := d.Get("enable"); enable.(bool) {
		// 	req := lb.EnableRequest{
		// 		LBID: lbId,
		// 	}
		// 	_, err := c.CloudBroker().LB().Enable(ctx, req)
		// 	if err != nil {
		// 		return diag.FromErr(err)
		// 	}
		// }
		// if start := d.Get("start"); start.(bool) {
		// 	if enable := d.Get("enable"); enable.(bool) {
		// 		req := lb.StartRequest{
		// 			LBID: lbId,
		// 		}
		// 		_, err := c.CloudBroker().LB().Start(ctx, req)
		// 		if err != nil {
		// 			return diag.FromErr(err)
		// 		}
		// 	} else {
		// 		return diag.Errorf("To start the LB, please, enable LB first.")
		// 	}
		// }

		// hasChanged = true
	case status.Destroying:
		return diag.Errorf("The LB is in progress with status: %s", lbRec.Status)
	case status.Destroyed:
		d.SetId("")
		return diag.Errorf("The resource cannot be read because it has been destroyed")
		// return resourceLBCreate(ctx, d, m)
	case status.Enabled:
	case status.Enabling:
	case status.Disabling:
	case status.Disabled:
		log.Debugf("The LB is in status: %s, troubles may occur with update. Please, enable LB first.", lbRec.Status)
	case status.Restoring:
	}

	if hasChanged {
		lbRec, err = utilityLBCheckPresence(ctx, d, m)
		if err != nil {
			d.SetId("")
			return diag.FromErr(err)
		}
	}

	flattenLB(d, lbRec)

	return nil
}

func resourceLBDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLBDelete called with lb id: %v", d.Get("lb_id").(int))

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

	c := m.(*controller.ControllerCfg)
	req := lb.DeleteRequest{
		LBID: uint64(d.Get("lb_id").(int)),
	}

	if permanently, ok := d.GetOk("permanently"); ok {
		req.Permanently = permanently.(bool)
	}

	_, err = c.CloudBroker().LB().Delete(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId("")

	return nil
}

func resourceLBUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceLBUpdate called for lb_id %s", d.Id())
	c := m.(*controller.ControllerCfg)

	if diags := checkParamsExistence(ctx, d, c); diags != nil {
		return diags
	}

	lbRec, err := utilityLBCheckPresence(ctx, d, m)
	if lbRec == nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	hasChanged := false

	switch lbRec.Status {
	case status.Modeled:
		return diag.Errorf("The LB is in status: %s, please, contact support for more information", lbRec.Status)
	case status.Creating:
	case status.Created:
	case status.Deleting:
	case status.Deleted:
		restore, ok := d.GetOk("restore")
		if ok && restore.(bool) {
			if err := resourceLbRestore(ctx, lbRec.ID, m); err != nil {
				return diag.FromErr(err)
			}
		}

		enable, ok := d.GetOk("enable")
		if ok && enable.(bool) {
			if err := resourceLbEnable(ctx, lbRec.ID, m); err != nil {
				return diag.FromErr(err)
			}
		}

		start, ok := d.GetOk("start")
		if ok && start.(bool) {
			if enable.(bool) {
				if err := resourceLbStart(ctx, lbRec.ID, m); err != nil {
					return diag.FromErr(err)
				}
			} else {
				return diag.Errorf("to start the LB, please, enable LB first.")
			}
		}

		hasChanged = true
	case status.Destroying:
		return diag.Errorf("The LB is in progress with status: %s", lbRec.Status)
	case status.Destroyed:
		d.SetId("")
		return diag.Errorf("The resource cannot be updated because it has been destroyed")
		// return resourceLBCreate(ctx, d, m)
	case status.Enabled:
	case status.Enabling:
	case status.Disabling:
	case status.Disabled:
		log.Debugf("The LB is in status: %s, troubles may occur with update. Please, enable LB first.", lbRec.Status)
	case status.Restoring:
	}

	if hasChanged {
		_, err = utilityLBCheckPresence(ctx, d, m)
		if err != nil {
			d.SetId("")
			return diag.FromErr(err)
		}
	}

	if d.HasChange("enable") {
		if err := resourceLbChangeEnable(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("ha_mode") {
		if err := resourceLbChangeHaMode(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("zone_id") {
		if err := resourceLbChangeZoneID(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("sysctl_params") {
		if err := resourceLbChangeSysctlParams(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("start") {
		if err := resourceLbChangeStart(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("desc") {
		if err := resourceLbChangeDesc(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("restart") {
		if err := resourceLbChangeRestart(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	if d.HasChange("config_reset") {
		if err := resourceLbChangeConfigReset(ctx, d, lbRec.ID, m); err != nil {
			return diag.FromErr(err)
		}
	}

	return resourceLBRead(ctx, d, m)
}

func resourceLbEnable(ctx context.Context, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	req := lb.EnableRequest{
		LBID: lbId,
	}
	_, err := c.CloudBroker().LB().Enable(ctx, req)
	return err
}

func resourceLbChangeSysctlParams(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	syscrlSliceMaps := d.Get("sysctl_params").([]interface{})
	res := make([]map[string]interface{}, 0, len(syscrlSliceMaps))
	for _, syscrlMap := range syscrlSliceMaps {
		tempMap := make(map[string]interface{})
		for k, v := range syscrlMap.(map[string]interface{}) {
			if intVal, err := strconv.Atoi(v.(string)); err == nil {
				tempMap[k] = intVal
				continue
			}
			tempMap[k] = v.(string)
		}
		res = append(res, tempMap)
	}
	if len(res) > 0 {
		req := lb.UpdateSysctParamsRequest{
			LBID:         lbId,
			SysctlParams: res,
		}
		_, err := c.CloudBroker().LB().UpdateSysctlParams(ctx, req)
		return err
	}
	return nil
}

func resourceLbDisable(ctx context.Context, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	req := lb.DisableRequest{
		LBID: lbId,
	}
	_, err := c.CloudBroker().LB().Disable(ctx, req)
	return err
}

func resourceLbRestore(ctx context.Context, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	restoreReq := lb.RestoreRequest{
		LBID: lbId,
	}
	_, err := c.CloudBroker().LB().Restore(ctx, restoreReq)
	return err
}

func resourceLbStart(ctx context.Context, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	req := lb.StartRequest{
		LBID: lbId,
	}
	_, err := c.CloudBroker().LB().Start(ctx, req)
	return err
}

func resourceLbChangeEnable(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	enable := d.Get("enable").(bool)

	if enable {
		if err := resourceLbEnable(ctx, lbId, m); err != nil {
			return err
		}
	} else {
		if err := resourceLbDisable(ctx, lbId, m); err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeHaMode(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	haModeOn := d.Get("ha_mode").(bool)

	if haModeOn {
		req := lb.HighlyAvailableRequest{
			LBID: lbId,
		}

		if _, err := c.CloudBroker().LB().HighlyAvailable(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeZoneID(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	start := d.Get("start").(bool)

	if start {
		reqStop := lb.StopRequest{
			LBID: uint64(d.Get("lb_id").(int)),
		}

		_, err := c.CloudBroker().LB().Stop(ctx, reqStop)
		if err != nil {
			return err
		}
	}

	req := lb.MigrateToZoneRequest{
		LBID:   uint64(d.Get("lb_id").(int)),
		ZoneID: uint64(d.Get("zone_id").(int)),
	}

	_, err := c.CloudBroker().LB().MigrateToZone(ctx, req)
	if err != nil {
		return err
	}

	if start {
		reqStart := lb.StartRequest{
			LBID: uint64(d.Get("lb_id").(int)),
		}

		_, err = c.CloudBroker().LB().Start(ctx, reqStart)
		if err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeStart(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	start := d.Get("start").(bool)

	if start {
		req := lb.StartRequest{LBID: lbId}
		if _, err := c.CloudBroker().LB().Start(ctx, req); err != nil {
			return err
		}
	} else {
		req := lb.StopRequest{LBID: lbId}
		if _, err := c.CloudBroker().LB().Stop(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeDesc(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)
	desc := d.Get("desc").(string)

	req := lb.UpdateRequest{
		LBID:        lbId,
		Description: desc,
	}

	if _, err := c.CloudBroker().LB().Update(ctx, req); err != nil {
		return err
	}

	return nil
}

func resourceLbChangeRestart(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	restart := d.Get("restart").(bool)
	if restart {
		req := lb.RestartRequest{
			LBID: lbId,
		}
		if safe, ok := d.GetOk("safe"); ok {
			req.Safe = safe.(bool)
		}

		if _, err := c.CloudBroker().LB().Restart(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeRestore(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	restore := d.Get("restore").(bool)
	if restore {
		req := lb.RestoreRequest{
			LBID: lbId,
		}

		if _, err := c.CloudBroker().LB().Restore(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

func resourceLbChangeConfigReset(ctx context.Context, d *schema.ResourceData, lbId uint64, m interface{}) error {
	c := m.(*controller.ControllerCfg)

	cfgReset := d.Get("config_reset").(bool)
	if cfgReset {
		req := lb.ConfigResetRequest{
			LBID: lbId,
		}

		if _, err := c.CloudBroker().LB().ConfigReset(ctx, req); err != nil {
			return err
		}
	}

	return nil
}

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

		CreateContext: resourceLBCreate,
		ReadContext:   resourceLBRead,
		UpdateContext: resourceLBUpdate,
		DeleteContext: resourceLBDelete,

		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: lbResourceSchemaMake(),
	}
}
