package accessgroup

import (
	"context"

	"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/sdn/acsgroups"
	"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/defsecpolicies"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/constants"
	"repository.basistech.ru/BASIS/terraform-provider-decort/internal/controller"
)

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

	req := acsgroups.CreateRequest{
		DisplayName: d.Get("display_name").(string),
		Comment:     d.Get("comment").(string),
	}

	accessGroup, err := c.SDN().AccessGroups().Create(ctx, req)
	if err != nil {
		d.SetId("")
		return diag.FromErr(err)
	}

	d.SetId(accessGroup.ID)

	if users, ok := d.GetOk("users"); ok {
		usersList := users.([]interface{})
		for _, userRaw := range usersList {
			userMap := userRaw.(map[string]interface{})
			userReq := acsgroups.UserAddRequest{
				GroupID:           accessGroup.ID,
				UserID:            userMap["user_id"].(string),
				AccessGroupRoleID: userMap["access_group_role_id"].(string),
			}
			_, err := c.SDN().AccessGroups().UserAdd(ctx, userReq)
			if err != nil {
				log.Warnf("resourceAccessGroupRead: failed to add users for access group %s: %v", d.Id(), err)

			}
		}
	}

	if defaultSecPolicy, ok := d.GetOk("default_security_policy"); ok {
		defaultSecPolicyList := defaultSecPolicy.([]interface{})
		if len(defaultSecPolicyList) > 0 {
			defaultSecPolicyMap := defaultSecPolicyList[0].(map[string]interface{})

			getReq := acsgroups.GetGroupRequest{
				GroupID: accessGroup.ID,
			}
			fullAccessGroup, err := c.SDN().AccessGroups().Get(ctx, getReq)
			if err != nil {
				return diag.FromErr(err)
			}

			if fullAccessGroup.DefaultSecurityPolicy.ID == "" {
				return diag.Errorf("default security policy not found for access group %s", accessGroup.ID)
			}

			updateReq := defsecpolicies.UpdateRequest{
				AccessGroupID: accessGroup.ID,
				VersionID:     uint64(fullAccessGroup.DefaultSecurityPolicy.VersionID),
			}

			if defaultAclDrop, ok := defaultSecPolicyMap["default_acl_drop"].(string); ok && defaultAclDrop != "" {
				updateReq.DefaultACLDrop = defaultAclDrop
			}

			if defaultOpenSessionDrop, ok := defaultSecPolicyMap["default_open_session_drop"].(bool); ok {
				updateReq.DefaultOpenSessionDrop = defaultOpenSessionDrop
			}

			_, err = c.SDN().DefaultSecurityPolicies().Update(ctx, updateReq)
			if err != nil {
				return diag.FromErr(err)
			}
		}
	}

	return resourceAccessGroupRead(ctx, d, m)
}

func resourceAccessGroupRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceAccessGroupRead: called access group with id %s", d.Id())

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

	userList, err := utilityAccessGroupUsersGet(ctx, accessGroup.ID, m)
	if err != nil {
		log.Warnf("resourceAccessGroupRead: failed to get users for access group %s: %v", d.Id(), err)
		userList = nil
	}

	flattenAccessGroupResource(d, accessGroup, userList)
	d.SetId(accessGroup.ID)

	return nil
}

func resourceAccessGroupUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceAccessGroupUpdate: called access group with id %s", d.Id())
	c := m.(*controller.ControllerCfg)

	req := acsgroups.UpdateRequest{
		AccessGroupID: d.Id(),
	}

	needUpdate := false

	if d.HasChange("display_name") {
		req.DisplayName = d.Get("display_name").(string)
		needUpdate = true
	}

	if d.HasChange("comment") {
		req.Comment = d.Get("comment").(string)
		needUpdate = true
	}

	var err error
	if needUpdate {
		_, err = c.SDN().AccessGroups().Update(ctx, req)
		if err != nil {
			d.SetId("")
			return diag.FromErr(err)
		}
	}

	if d.HasChange("users") {
		oldUsers, newUsers := d.GetChange("users")
		oldUsersList := oldUsers.([]interface{})
		newUsersList := newUsers.([]interface{})

		oldUsersMap := make(map[string]string)
		for _, userRaw := range oldUsersList {
			userMap := userRaw.(map[string]interface{})
			userID := userMap["user_id"].(string)
			oldUsersMap[userID] = userMap["access_group_role_id"].(string)
		}

		newUsersMap := make(map[string]string)
		for _, userRaw := range newUsersList {
			userMap := userRaw.(map[string]interface{})
			userID := userMap["user_id"].(string)
			newUsersMap[userID] = userMap["access_group_role_id"].(string)
		}

		for userID := range oldUsersMap {
			if _, exists := newUsersMap[userID]; !exists {
				userDeleteReq := acsgroups.UserDeleteRequest{
					GroupID: d.Id(),
					UserID:  userID,
				}
				_, err := c.SDN().AccessGroups().UserDelete(ctx, userDeleteReq)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}

		for userID, roleID := range newUsersMap {
			if oldRoleID, exists := oldUsersMap[userID]; !exists || oldRoleID != roleID {
				if !exists {
					userAddReq := acsgroups.UserAddRequest{
						GroupID:           d.Id(),
						UserID:            userID,
						AccessGroupRoleID: roleID,
					}
					_, err := c.SDN().AccessGroups().UserAdd(ctx, userAddReq)
					if err != nil {
						return diag.FromErr(err)
					}
				} else if oldRoleID != roleID {
					userUpdateRoleReq := acsgroups.UserUpdateRoleRequest{
						GroupID:           d.Id(),
						UserID:            userID,
						AccessGroupRoleID: roleID,
					}
					_, err := c.SDN().AccessGroups().UserUpdateRole(ctx, userUpdateRoleReq)
					if err != nil {
						return diag.FromErr(err)
					}
				}
			}
		}
	}

	if d.HasChange("default_security_policy") {
		defaultSecPolicy, ok := d.GetOk("default_security_policy")
		if ok {
			defaultSecPolicyList := defaultSecPolicy.([]interface{})
			if len(defaultSecPolicyList) > 0 {
				defaultSecPolicyMap := defaultSecPolicyList[0].(map[string]interface{})

				versionID, ok := defaultSecPolicyMap["version_id"].(int)
				if !ok || versionID == 0 {
					return diag.Errorf("version_id not found in default_security_policy for access group %s", d.Id())
				}

				updateReq := defsecpolicies.UpdateRequest{
					AccessGroupID: d.Id(),
					VersionID:     uint64(versionID),
				}

				if defaultAclDrop, ok := defaultSecPolicyMap["default_acl_drop"].(string); ok && defaultAclDrop != "" {
					updateReq.DefaultACLDrop = defaultAclDrop
				}

				if defaultOpenSessionDrop, ok := defaultSecPolicyMap["default_open_session_drop"].(bool); ok {
					updateReq.DefaultOpenSessionDrop = defaultOpenSessionDrop
				}

				_, err = c.SDN().DefaultSecurityPolicies().Update(ctx, updateReq)
				if err != nil {
					return diag.FromErr(err)
				}
			}
		}
	}

	return resourceAccessGroupRead(ctx, d, m)
}

func resourceAccessGroupDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
	log.Debugf("resourceAccessGroupDelete: called access group with id %s", d.Id())
	c := m.(*controller.ControllerCfg)

	req := acsgroups.DeleteRequest{
		GroupID: d.Id(),
	}

	_, err := c.SDN().AccessGroups().Delete(ctx, req)
	if err != nil {
		return diag.FromErr(err)
	}

	d.SetId("")

	return nil
}

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

		CreateContext: resourceAccessGroupCreate,
		ReadContext:   resourceAccessGroupRead,
		UpdateContext: resourceAccessGroupUpdate,
		DeleteContext: resourceAccessGroupDelete,

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

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

		Schema: resourceAccessGroupSchemaMake(),
	}
}
