/*
 * Copyright 2025 coze-dev Authors
 *
 * 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.
 */

package main

import (
	"errors"
	"fmt"
	"os"
	"time"

	"github.com/volcengine/volcengine-go-sdk/service/vpc"
	"github.com/volcengine/volcengine-go-sdk/volcengine"
)

func CreatePrivateNetwork(vName, ts, zoneID string) (string, error) {
	if os.Getenv("VE_VPC_ID") != "" {
		return os.Getenv("VE_VPC_ID"), nil
	}

	svc := vpc.New(sess)

	reqTags := &vpc.TagForCreateVpcInput{
		Key:   volcengine.String("opencoze"),
		Value: volcengine.String("1"),
	}

	newVpcName := vName + ts
	createVpcInput := &vpc.CreateVpcInput{
		CidrBlock:   volcengine.String("172.16.0.0/12"),
		ClientToken: volcengine.String(newVpcName),
		ProjectName: volcengine.String(projectName),
		Tags:        []*vpc.TagForCreateVpcInput{reqTags},
		VpcName:     volcengine.String(newVpcName),
	}

	resp, err := svc.CreateVpc(createVpcInput)
	if err != nil {
		return "", err
	}

	if resp.VpcId == nil {
		return "", errors.New("[VPC] VpcId is empty")
	}

	return *resp.VpcId, nil
}

func CheckVpcStatus(vpcID string) {
	svc := vpc.New(sess)

	describeVpcAttributesInput := &vpc.DescribeVpcAttributesInput{
		VpcId: volcengine.String(vpcID),
	}

	for {
		resp, err := svc.DescribeVpcAttributes(describeVpcAttributesInput)
		if resp.Status != nil && *resp.Status != "Available" {
			fmt.Printf("[VPC] VPC(%s) is %s, waiting for it to become ready... \n", vpcID, *resp.Status)
			time.Sleep(retryTime)
			continue
		}
		if err != nil {
			fmt.Printf("[VPC] get vpc id = %s failed, err= %s will retry\n", vpcID, err.Error())
			time.Sleep(retryTime)
			continue
		}

		if resp.Status == nil {
			fmt.Printf("[VPC] get vpc id = %s failed, status is nil will retry\n", vpcID)
			time.Sleep(retryTime)
			continue
		}

		if *resp.Status == "Available" {
			break
		}
	}
}

func CreateSubnet(vName, ts, vpcID, zoneID string) (string, error) {
	if os.Getenv("VE_SUBNET_ID") != "" {
		return os.Getenv("VE_SUBNET_ID"), nil
	}

	reqSubTags := &vpc.TagForCreateSubnetInput{
		Key:   volcengine.String("opencoze"),
		Value: volcengine.String("1"),
	}

	newSubnetName := vName + "-subnet-" + ts
	createSubnetInput := &vpc.CreateSubnetInput{
		CidrBlock:   volcengine.String("172.16.0.0/24"),
		ClientToken: volcengine.String(newSubnetName),
		SubnetName:  volcengine.String(newSubnetName),
		Tags:        []*vpc.TagForCreateSubnetInput{reqSubTags},
		VpcId:       volcengine.String(vpcID),
		ZoneId:      volcengine.String(zoneID),
	}

	svc := vpc.New(sess)
	resp, err := svc.CreateSubnet(createSubnetInput)
	if err != nil {
		return "", err
	}

	if resp.SubnetId == nil {
		return "", errors.New("[Subnet] SubnetId is empty")
	}

	return *resp.SubnetId, nil
}

func CreateNetworkACL(vName, ts, vpcID string) (string, error) {
	if os.Getenv("VE_ACL_ID") != "" {
		return os.Getenv("VE_ACL_ID"), nil
	}

	svc := vpc.New(sess)

	reqTags := &vpc.TagForCreateNetworkAclInput{
		Key:   volcengine.String("opencoze"),
		Value: volcengine.String("1"),
	}

	newACLName := vName + "-acl-" + ts

	createNetworkAclInput := &vpc.CreateNetworkAclInput{
		NetworkAclName: volcengine.String(newACLName),
		ProjectName:    volcengine.String(projectName),
		Tags:           []*vpc.TagForCreateNetworkAclInput{reqTags},
		VpcId:          volcengine.String(vpcID),
	}

	resp, err := svc.CreateNetworkAcl(createNetworkAclInput)
	if err != nil {
		return "", err
	}

	if resp.NetworkAclId == nil {
		return "", errors.New("NetworkAclId is empty")
	}

	return *resp.NetworkAclId, nil
}

func CheckACLStatus(aclID string) {
	svc := vpc.New(sess)

	describeNetworkAclAttributesInput := &vpc.DescribeNetworkAclAttributesInput{
		NetworkAclId: volcengine.String(aclID),
	}

	for {
		resp, err := svc.DescribeNetworkAclAttributes(describeNetworkAclAttributesInput)
		if resp.NetworkAclAttribute != nil && resp.NetworkAclAttribute.Status != nil && *resp.NetworkAclAttribute.Status != "Available" {
			fmt.Printf("[ACL] ACL(%s) is %s, waiting for it to become ready... \n", aclID, *resp.NetworkAclAttribute.Status)
			time.Sleep(retryTime)
			continue
		}

		if err != nil {
			fmt.Printf("[ACL] will retry get acl = %s failed, err= %s\n", aclID, err.Error())
			time.Sleep(retryTime)
			continue
		}

		if resp.NetworkAclAttribute == nil || resp.NetworkAclAttribute.Status == nil {
			fmt.Printf("[ACL] ACL(%s) Status is nil, will retry\n", aclID)
			time.Sleep(retryTime)
			continue
		}

		if *resp.NetworkAclAttribute.Status == "Available" {
			break
		}
	}
}

func AttachSubnetToACL(aclID, subnetID string) error {
	svc := vpc.New(sess)

	reqResource := &vpc.ResourceForAssociateNetworkAclInput{
		ResourceId: volcengine.String(subnetID),
	}
	associateNetworkAclInput := &vpc.AssociateNetworkAclInput{
		NetworkAclId: volcengine.String(aclID),
		Resource:     []*vpc.ResourceForAssociateNetworkAclInput{reqResource},
	}

	resp, err := svc.AssociateNetworkAcl(associateNetworkAclInput)
	if resp.Metadata != nil && resp.Metadata.Error != nil &&
		resp.Metadata.Error.Code == "InvalidResource.Associated" {
		return nil
	}

	if err != nil {
		return err
	}

	return nil
}

func CreateSafeGroup(ts, vpcID string) (string, error) {
	if os.Getenv("VE_SAFE_GROUP_ID") != "" {
		return os.Getenv("VE_SAFE_GROUP_ID"), nil
	}

	svc := vpc.New(sess)
	reqTags := &vpc.TagForCreateSecurityGroupInput{
		Key:   volcengine.String("opencoze"),
		Value: volcengine.String("1"),
	}
	createSecurityGroupInput := &vpc.CreateSecurityGroupInput{
		ProjectName:       volcengine.String(projectName),
		SecurityGroupName: volcengine.String("opencoze-sg-" + ts),
		Tags:              []*vpc.TagForCreateSecurityGroupInput{reqTags},
		VpcId:             volcengine.String(vpcID),
	}

	resp, err := svc.CreateSecurityGroup(createSecurityGroupInput)
	if err != nil {
		return "", err
	}

	if resp.SecurityGroupId == nil {
		return "", errors.New("SecurityGroupId is empty")
	}

	return *resp.SecurityGroupId, nil
}

func CheckSafeGroupStatus(sgID string) {
	for {
		svc := vpc.New(sess)
		describeSecurityGroupAttributesInput := &vpc.DescribeSecurityGroupAttributesInput{
			SecurityGroupId: volcengine.String(sgID),
		}

		// 复制代码运行示例，请自行打印API返回值。
		resp, err := svc.DescribeSecurityGroupAttributes(describeSecurityGroupAttributesInput)
		if err != nil {
			fmt.Printf("[SafeGroup] will retry get safe group = %s failed, err= %s\n", sgID, err.Error())
			time.Sleep(retryTime)
			continue
		}

		if resp.Status == nil || *resp.Status != "Available" {
			fmt.Printf("[SafeGroup] safe group(%s) is %s, waiting for it to become ready... \n", sgID, *resp.Status)
			time.Sleep(retryTime)
			continue
		}

		break
	}
}

func CreateSafeGroupRule(sgID string) error {
	svc := vpc.New(sess)

	type Rule struct {
		PortStart int64
		PortEnd   int64
		Protocol  string
	}

	defaultRules := []Rule{
		{
			PortStart: 8000,
			PortEnd:   10000,
			Protocol:  "tcp",
		},
		{
			PortStart: 80,
			PortEnd:   80,
			Protocol:  "tcp",
		},
		{
			PortStart: 22,
			PortEnd:   22,
			Protocol:  "tcp",
		},
		{
			PortStart: 443,
			PortEnd:   443,
			Protocol:  "tcp",
		},
		{
			PortStart: 3389,
			PortEnd:   3389,
			Protocol:  "tcp",
		},

		{
			PortStart: -1,
			PortEnd:   -1,
			Protocol:  "ALL",
		},
	}

	for _, rule := range defaultRules {
		authorizeSecurityGroupIngressInput := &vpc.AuthorizeSecurityGroupIngressInput{
			CidrIp:          volcengine.String("0.0.0.0/0"),
			PortEnd:         volcengine.Int64(rule.PortEnd),
			PortStart:       volcengine.Int64(rule.PortStart),
			Protocol:        volcengine.String(rule.Protocol),
			SecurityGroupId: volcengine.String(sgID),
		}

		if rule.PortStart == -1 {
			authorizeSecurityGroupIngressInput.SourceGroupId = &sgID
			authorizeSecurityGroupIngressInput.CidrIp = nil
		}

		resp, err := svc.AuthorizeSecurityGroupIngress(authorizeSecurityGroupIngressInput)
		if resp != nil && resp.Metadata != nil && resp.Metadata.Error != nil &&
			resp.Metadata.Error.Code == "InvalidSecurityRule.Conflict" {
			continue
		}
		if err != nil {
			return err
		}
	}

	return nil
}
