// Copyright 2022-2025 The sacloud/iaas-api-go 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 fake

import (
	"context"
	"net"
	"time"

	"github.com/sacloud/iaas-api-go"
	"github.com/sacloud/iaas-api-go/types"
)

// Find is fake implementation
func (o *InternetOp) Find(ctx context.Context, zone string, conditions *iaas.FindCondition) (*iaas.InternetFindResult, error) {
	results, _ := find(o.key, zone, conditions)
	var values []*iaas.Internet
	for _, res := range results {
		dest := &iaas.Internet{}
		copySameNameField(res, dest)
		values = append(values, dest)
	}
	return &iaas.InternetFindResult{
		Total:    len(results),
		Count:    len(results),
		From:     0,
		Internet: values,
	}, nil
}

// Create is fake implementation
func (o *InternetOp) Create(ctx context.Context, zone string, param *iaas.InternetCreateRequest) (*iaas.Internet, error) {
	if param.NetworkMaskLen == 0 {
		param.NetworkMaskLen = 28
	}
	if param.BandWidthMbps == 0 {
		param.BandWidthMbps = 100
	}

	result := &iaas.Internet{}
	copySameNameField(param, result)
	fill(result, fillID, fillCreatedAt)

	// assign global address
	subnet := pool().nextSubnet(result.NetworkMaskLen)

	// create switch
	swOp := NewSwitchOp()
	sw, err := swOp.Create(ctx, zone, &iaas.SwitchCreateRequest{
		Name:           result.Name,
		NetworkMaskLen: subnet.networkMaskLen,
		DefaultRoute:   subnet.defaultRoute,
	})
	if err != nil {
		return nil, err
	}

	sSubnet := &iaas.SwitchSubnet{
		ID:                   pool().generateID(),
		DefaultRoute:         subnet.defaultRoute,
		NetworkAddress:       subnet.networkAddress,
		NetworkMaskLen:       subnet.networkMaskLen,
		Internet:             result,
		AssignedIPAddressMax: subnet.addresses[len(subnet.addresses)-1],
		AssignedIPAddressMin: subnet.addresses[0],
	}
	sw.Subnets = append(sw.Subnets, sSubnet)

	// for Internet.Switch
	switchInfo := &iaas.SwitchInfo{}
	copySameNameField(sw, switchInfo)

	iSubnet := &iaas.InternetSubnet{
		ID:             sSubnet.ID,
		DefaultRoute:   sSubnet.DefaultRoute,
		NetworkAddress: sSubnet.NetworkAddress,
		NetworkMaskLen: sSubnet.NetworkMaskLen,
	}
	switchInfo.Subnets = []*iaas.InternetSubnet{iSubnet}
	result.Switch = switchInfo

	// for raw subnet
	rSubnet := &iaas.Subnet{}
	copySameNameField(sSubnet, rSubnet)
	for _, ip := range subnet.addresses {
		rSubnet.IPAddresses = append(rSubnet.IPAddresses, &iaas.SubnetIPAddress{IPAddress: ip})
	}
	rSubnet.SwitchID = sw.ID
	rSubnet.InternetID = result.ID

	putSwitch(zone, sw)
	putInternet(zone, result)
	putSubnet(zone, rSubnet)
	return result, nil
}

// Read is fake implementation
func (o *InternetOp) Read(ctx context.Context, zone string, id types.ID) (*iaas.Internet, error) {
	value := getInternetByID(zone, id)
	if value == nil {
		return nil, newErrorNotFound(o.key, id)
	}
	dest := &iaas.Internet{}
	copySameNameField(value, dest)
	return dest, nil
}

// Update is fake implementation
func (o *InternetOp) Update(ctx context.Context, zone string, id types.ID, param *iaas.InternetUpdateRequest) (*iaas.Internet, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}
	copySameNameField(param, value)

	putInternet(zone, value)
	return value, nil
}

// Delete is fake implementation
func (o *InternetOp) Delete(ctx context.Context, zone string, id types.ID) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	// check subnets/ipv6nets
	if len(value.Switch.Subnets) > 1 {
		return newErrorBadRequest(ResourceInternet, value.ID, "Internet resource still have Subnets")
	}
	if len(value.Switch.IPv6Nets) > 1 {
		return newErrorBadRequest(ResourceInternet, value.ID, "Internet resource still have Subnets")
	}

	swOp := NewSwitchOp()
	if err := swOp.Delete(ctx, zone, value.Switch.ID); err != nil {
		return err
	}

	ds().Delete(o.key, zone, id)
	return nil
}

// UpdateBandWidth is fake implementation
func (o *InternetOp) UpdateBandWidth(ctx context.Context, zone string, id types.ID, param *iaas.InternetUpdateBandWidthRequest) (*iaas.Internet, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	value.BandWidthMbps = param.BandWidthMbps
	putInternet(zone, value)
	return value, nil
}

// AddSubnet is fake implementation
func (o *InternetOp) AddSubnet(ctx context.Context, zone string, id types.ID, param *iaas.InternetAddSubnetRequest) (*iaas.InternetSubnetOperationResult, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	// assign global address
	subnet := pool().nextSubnetFull(param.NetworkMaskLen, param.NextHop)

	// create switch
	swOp := NewSwitchOp()
	sw, err := swOp.Read(ctx, zone, value.Switch.ID)
	if err != nil {
		return nil, err
	}

	sSubnet := &iaas.SwitchSubnet{
		ID:                   pool().generateID(),
		NetworkAddress:       subnet.networkAddress,
		NetworkMaskLen:       subnet.networkMaskLen,
		NextHop:              param.NextHop,
		StaticRoute:          param.NextHop,
		Internet:             value,
		AssignedIPAddressMax: subnet.addresses[len(subnet.addresses)-1],
		AssignedIPAddressMin: subnet.addresses[0],
	}
	sw.Subnets = append(sw.Subnets, sSubnet)

	// for Internet.Switch
	iSubnet := &iaas.InternetSubnet{
		ID:             sSubnet.ID,
		DefaultRoute:   sSubnet.DefaultRoute,
		NetworkAddress: sSubnet.NetworkAddress,
		NetworkMaskLen: sSubnet.NetworkMaskLen,
		NextHop:        param.NextHop,
		StaticRoute:    param.NextHop,
	}
	value.Switch.Subnets = append(value.Switch.Subnets, iSubnet)

	// for raw subnet
	rSubnet := &iaas.Subnet{}
	copySameNameField(sSubnet, rSubnet)
	for _, ip := range subnet.addresses {
		rSubnet.IPAddresses = append(rSubnet.IPAddresses, &iaas.SubnetIPAddress{IPAddress: ip})
	}
	rSubnet.SwitchID = sw.ID
	rSubnet.InternetID = value.ID

	putSwitch(zone, sw)
	putInternet(zone, value)
	putSubnet(zone, rSubnet)

	return &iaas.InternetSubnetOperationResult{
		ID:             sSubnet.ID,
		NextHop:        param.NextHop,
		StaticRoute:    param.NextHop,
		NetworkMaskLen: sSubnet.NetworkMaskLen,
		NetworkAddress: sSubnet.NetworkAddress,
		IPAddresses:    subnet.addresses,
	}, nil
}

// UpdateSubnet is fake implementation
func (o *InternetOp) UpdateSubnet(ctx context.Context, zone string, id types.ID, subnetID types.ID, param *iaas.InternetUpdateSubnetRequest) (*iaas.InternetSubnetOperationResult, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}
	// create switch
	swOp := NewSwitchOp()
	sw, err := swOp.Read(ctx, zone, value.Switch.ID)
	if err != nil {
		return nil, err
	}

	rSubnet, err := NewSubnetOp().Read(ctx, zone, subnetID)
	if err != nil {
		return nil, err
	}

	rSubnet.NextHop = param.NextHop

	var nwMaskLen int
	var nwAddress, minAddr, maxAddr string
	var addresses []string

	for _, subnet := range sw.Subnets {
		if subnet.ID == subnetID {
			subnet.NextHop = param.NextHop
			subnet.StaticRoute = param.NextHop

			minAddr = subnet.AssignedIPAddressMin
			maxAddr = subnet.AssignedIPAddressMax
			nwMaskLen = subnet.NetworkMaskLen
			nwAddress = subnet.NetworkAddress
		}
	}

	for _, subnet := range value.Switch.Subnets {
		if subnet.ID == subnetID {
			subnet.NextHop = param.NextHop
			subnet.StaticRoute = param.NextHop
		}
	}

	baseIP := net.ParseIP(minAddr).To4()
	min := baseIP[3]
	max := net.ParseIP(maxAddr).To4()[3]

	var i byte
	for (min + i) <= max { // 境界含む
		ip := net.IPv4(baseIP[0], baseIP[1], baseIP[2], baseIP[3]+i)
		addresses = append(addresses, ip.String())
		i++
	}

	putSubnet(zone, rSubnet)
	putSwitch(zone, sw)
	putInternet(zone, value)
	return &iaas.InternetSubnetOperationResult{
		ID:             subnetID,
		NextHop:        param.NextHop,
		StaticRoute:    param.NextHop,
		NetworkMaskLen: nwMaskLen,
		NetworkAddress: nwAddress,
		IPAddresses:    addresses,
	}, nil
}

// DeleteSubnet is fake implementation
func (o *InternetOp) DeleteSubnet(ctx context.Context, zone string, id types.ID, subnetID types.ID) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	// create switch
	swOp := NewSwitchOp()
	sw, err := swOp.Read(ctx, zone, value.Switch.ID)
	if err != nil {
		return err
	}

	var sSubnets []*iaas.SwitchSubnet
	for _, subnet := range sw.Subnets {
		if subnet.ID != subnetID {
			sSubnets = append(sSubnets, subnet)
		}
	}
	sw.Subnets = sSubnets

	var iSubnets []*iaas.InternetSubnet
	for _, subnet := range value.Switch.Subnets {
		if subnet.ID != subnetID {
			iSubnets = append(iSubnets, subnet)
		}
	}
	value.Switch.Subnets = iSubnets

	ds().Delete(ResourceSubnet, zone, subnetID)
	putSwitch(zone, sw)
	putInternet(zone, value)
	return nil
}

// Monitor is fake implementation
func (o *InternetOp) Monitor(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.RouterActivity, error) {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	now := time.Now().Truncate(time.Second)
	m := now.Minute() % 5
	if m != 0 {
		now.Add(time.Duration(m) * time.Minute)
	}

	res := &iaas.RouterActivity{}
	for i := 0; i < 5; i++ {
		res.Values = append(res.Values, &iaas.MonitorRouterValue{
			Time: now.Add(time.Duration(i*-5) * time.Minute),
			In:   float64(random(1000)),
			Out:  float64(random(1000)),
		})
	}

	return res, nil
}

// MonitorRouter is fake implementation
func (o *InternetOp) MonitorRouter(ctx context.Context, zone string, id types.ID, condition *iaas.MonitorCondition) (*iaas.RouterActivity, error) {
	return o.Monitor(ctx, zone, id, condition)
}

// EnableIPv6 is fake implementation
func (o *InternetOp) EnableIPv6(ctx context.Context, zone string, id types.ID) (*iaas.IPv6NetInfo, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	ipv6net := &iaas.IPv6Net{
		ID:            pool().generateID(),
		IPv6Prefix:    "2001:db8:11aa:22bb::/64",
		IPv6PrefixLen: 64,
	}
	putIPv6Net(zone, ipv6net)

	ipv6netInfo := &iaas.IPv6NetInfo{}
	copySameNameField(ipv6net, ipv6netInfo)

	value.Switch.IPv6Nets = []*iaas.IPv6NetInfo{ipv6netInfo}
	putInternet(zone, value)
	return ipv6netInfo, nil
}

// DisableIPv6 is fake implementation
func (o *InternetOp) DisableIPv6(ctx context.Context, zone string, id types.ID, ipv6netID types.ID) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	if len(value.Switch.IPv6Nets) == 0 {
		return nil
	}

	ds().Delete(ResourceIPv6Net, zone, value.Switch.IPv6Nets[0].ID)
	value.Switch.IPv6Nets = []*iaas.IPv6NetInfo{}
	putInternet(zone, value)
	return nil
}
