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

import (
	"context"
	"strconv"

	"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/cloudapi/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/dc"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/status"
)

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

	haveRGID, err := existRGID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveRGID {
		return diag.Errorf("resourceLBCreate: can't create LB because RGID %d is not allowed or does not exist", d.Get("rg_id").(int))
	}

	haveExtNetID, err := existExtNetID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveExtNetID {
		return diag.Errorf("resourceLBCreate: can't create LB because ExtNetID %d is not allowed or does not exist", d.Get("extnet_id").(int))
	}

	haveVins, err := existViNSID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveVins {
		return diag.Errorf("resourceLBCreate: can't create LB because ViNSID %d is not allowed or does not exist", d.Get("vins_id").(int))
	}

	c := m.(*controller.ControllerCfg)
	req := lb.CreateRequest{
		Name:     d.Get("name").(string),
		RGID:     uint64(d.Get("rg_id").(int)),
		ExtNetID: uint64(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 haMode, ok := d.GetOk("ha_mode"); ok {
		req.HighlyAvailable = haMode.(bool)
	}
	if zoneID, ok := d.GetOk("zone_id"); ok {
		req.ZoneID = uint64(zoneID.(int))
	}
	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.CloudAPI().LB().Create(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

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

	w := dc.Warnings{}

	if enable, ok := d.GetOk("enable"); ok {
		req := lb.DisableEnableRequest{
			LBID: lbId,
		}

		if enable.(bool) {
			_, err := c.CloudAPI().LB().Enable(ctx, req)
			if err != nil {
				w.Add(err)
			}
		} else {
			_, err := c.CloudAPI().LB().Disable(ctx, req)
			if err != nil {
				w.Add(err)
			}
		}

		if start, ok := d.GetOk("start"); ok && enable.(bool) {
			if start.(bool) {
				req := lb.StartRequest{LBID: lbId}
				_, err := c.CloudAPI().LB().Start(ctx, req)
				if err != nil {
					w.Add(err)
				}
			} else {
				req := lb.StopRequest{LBID: lbId}
				_, err := c.CloudAPI().LB().Stop(ctx, req)
				if err != nil {
					w.Add(err)
				}
			}
		}
	}

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

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

	// 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}
		// enableReq := lb.DisableEnableRequest{LBID: lbId}

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

		// 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 {
		lbRec, err = utilityLBCheckPresence(ctx, d, m)
		if err != nil {
			d.SetId("")
			return diag.FromErr(err)
		}
	}

	flattenResourceLB(d, lbRec)

	return nil
}

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

	c := m.(*controller.ControllerCfg)
	lbId, _ := strconv.ParseUint(d.Id(), 10, 64)
	req := lb.DeleteRequest{
		LBID: lbId,
	}

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

	_, err := c.CloudAPI().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")
	c := m.(*controller.ControllerCfg)

	haveRGID, err := existRGID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveRGID {
		return diag.Errorf("resourceLBUpdate: can't update LB because RGID %d is not allowed or does not exist", d.Get("rg_id").(int))
	}

	haveExtNetID, err := existExtNetID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveExtNetID {
		return diag.Errorf("resourceLBUpdate: can't update LB because ExtNetID %d is not allowed or does not exist", d.Get("extnet_id").(int))
	}

	haveVins, err := existViNSID(ctx, d, m)
	if err != nil {
		return diag.FromErr(err)
	}

	if !haveVins {
		return diag.Errorf("resourceLBUpdate: can't update LB because ViNSID %d is not allowed or does not exist", d.Get("vins_id").(int))
	}

	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:
		if restore, ok := d.GetOk("restore"); ok && restore.(bool) {
			restoreReq := lb.RestoreRequest{LBID: lbRec.ID}

			_, err := c.CloudAPI().LB().Restore(ctx, restoreReq)
			if err != nil {
				return diag.FromErr(err)
			}
		}
		if enable, ok := d.GetOk("enable"); ok {
			req := lb.DisableEnableRequest{
				LBID: lbRec.ID,
			}

			if enable.(bool) {
				_, err := c.CloudAPI().LB().Enable(ctx, req)
				if err != nil {
					return diag.FromErr(err)
				}
			} else {
				_, err := c.CloudAPI().LB().Disable(ctx, req)
				if err != nil {
					return diag.FromErr(err)
				}
			}

			if start, ok := d.GetOk("start"); ok && enable.(bool) {
				if start.(bool) {
					req := lb.StartRequest{LBID: lbRec.ID}
					_, err := c.CloudAPI().LB().Start(ctx, req)
					if err != nil {
						return diag.FromErr(err)
					}
				} else {
					req := lb.StopRequest{LBID: lbRec.ID}
					_, err := c.CloudAPI().LB().Stop(ctx, req)
					if err != nil {
						return diag.FromErr(err)
					}
				}
			}
		}
		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("ha_mode") {
		hamode := d.Get("ha_mode").(bool)
		if hamode {
			req := lb.HighlyAvailableRequest{
				LBID: uint64(d.Get("lb_id").(int)),
			}
			_, err := c.CloudAPI().LB().HighlyAvailable(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("sysctl_params") {
		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:         uint64(d.Get("lb_id").(int)),
				SysctlParams: res,
			}
			_, err := c.CloudAPI().LB().UpdateSysctlParams(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("enable") {
		enable := d.Get("enable").(bool)
		req := lb.DisableEnableRequest{
			LBID: uint64(d.Get("lb_id").(int)),
		}
		if enable {
			_, err := c.CloudAPI().LB().Enable(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		} else {
			_, err := c.CloudAPI().LB().Disable(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("start") {
		start := d.Get("start").(bool)
		lbId := uint64(d.Get("lb_id").(int))
		if start {
			req := lb.StartRequest{LBID: lbId}
			_, err := c.CloudAPI().LB().Start(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		} else {
			req := lb.StopRequest{LBID: lbId}
			_, err := c.CloudAPI().LB().Stop(ctx, req)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	if d.HasChange("desc") {
		req := lb.UpdateRequest{
			LBID:        uint64(d.Get("lb_id").(int)),
			Description: d.Get("desc").(string),
		}

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

	if d.HasChange("zone_id") {
		start := d.Get("start").(bool)

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

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

			start = false
		}

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

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

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

			_, err := c.CloudAPI().LB().Start(ctx, reqStart)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

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

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

	if d.HasChange("restore") {
		restore := d.Get("restore").(bool)
		if restore {
			req := lb.RestoreRequest{
				LBID: uint64(d.Get("lb_id").(int)),
			}

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

	if d.HasChange("config_reset") {
		cfgReset := d.Get("config_reset").(bool)
		if cfgReset {
			req := lb.ConfigResetRequest{
				LBID: uint64(d.Get("lb_id").(int)),
			}

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

	return resourceLBRead(ctx, d, m)
}

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