// 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"
	"fmt"
	"time"

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

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

// Create is fake implementation
func (o *MobileGatewayOp) Create(ctx context.Context, zone string, param *iaas.MobileGatewayCreateRequest) (*iaas.MobileGateway, error) {
	result := &iaas.MobileGateway{}
	copySameNameField(param, result)
	fill(result, fillID, fillCreatedAt)

	result.Availability = types.Availabilities.Available
	result.Class = "mobilegateway"
	result.ZoneID = zoneIDs[zone]
	result.SettingsHash = ""

	// set interface
	ifOp := NewInterfaceOp()
	iface, err := ifOp.Create(ctx, zone, &iaas.InterfaceCreateRequest{ServerID: result.ID})
	if err != nil {
		return nil, newErrorConflict(o.key, types.ID(0), err.Error())
	}
	if err := ifOp.ConnectToSharedSegment(ctx, zone, iface.ID); err != nil {
		return nil, newErrorConflict(o.key, types.ID(0), err.Error())
	}
	iface, err = ifOp.Read(ctx, zone, iface.ID)
	if err != nil {
		return nil, newErrorConflict(o.key, types.ID(0), err.Error())
	}
	ifaceView := &iaas.MobileGatewayInterface{}
	copySameNameField(iface, ifaceView)
	result.Interfaces = append(result.Interfaces, ifaceView)

	putMobileGateway(zone, result)
	return result, nil
}

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

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

	putMobileGateway(zone, value)
	return value, nil
}

// UpdateSettings is fake implementation
func (o *MobileGatewayOp) UpdateSettings(ctx context.Context, zone string, id types.ID, param *iaas.MobileGatewayUpdateSettingsRequest) (*iaas.MobileGateway, error) {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}
	copySameNameField(param, value)
	fill(value, fillModifiedAt)

	putMobileGateway(zone, value)
	return value, nil
}

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

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

// Config is fake implementation
func (o *MobileGatewayOp) Config(ctx context.Context, zone string, id types.ID) error {
	_, err := o.Read(ctx, zone, id)
	return err
}

// Boot is fake implementation
func (o *MobileGatewayOp) Boot(ctx context.Context, zone string, id types.ID) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}
	if value.InstanceStatus.IsUp() {
		return newErrorConflict(o.key, id, "Boot is failed")
	}

	startPowerOn(o.key, zone, func() (interface{}, error) {
		return o.Read(context.Background(), zone, id)
	})

	return err
}

// Shutdown is fake implementation
func (o *MobileGatewayOp) Shutdown(ctx context.Context, zone string, id types.ID, shutdownOption *iaas.ShutdownOption) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}
	if !value.InstanceStatus.IsUp() {
		return newErrorConflict(o.key, id, "Shutdown is failed")
	}

	startPowerOff(o.key, zone, func() (interface{}, error) {
		return o.Read(context.Background(), zone, id)
	})

	return err
}

// Reset is fake implementation
func (o *MobileGatewayOp) Reset(ctx context.Context, zone string, id types.ID) error {
	value, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}
	if !value.InstanceStatus.IsUp() {
		return newErrorConflict(o.key, id, "Reset is failed")
	}

	startPowerOn(o.key, zone, func() (interface{}, error) {
		return o.Read(context.Background(), zone, id)
	})

	return nil
}

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

	for _, nic := range value.Interfaces {
		if nic.Index == 1 {
			return newErrorBadRequest(o.key, id, fmt.Sprintf("nic[%d] already connected to switch", 1))
		}
	}

	// find switch
	swOp := NewSwitchOp()
	_, err = swOp.Read(ctx, zone, switchID)
	if err != nil {
		return fmt.Errorf("ConnectToSwitch is failed: %s", err)
	}

	// create interface
	ifOp := NewInterfaceOp()
	iface, err := ifOp.Create(ctx, zone, &iaas.InterfaceCreateRequest{ServerID: id})
	if err != nil {
		return newErrorConflict(o.key, types.ID(0), err.Error())
	}

	if err := ifOp.ConnectToSwitch(ctx, zone, iface.ID, switchID); err != nil {
		return newErrorConflict(o.key, types.ID(0), err.Error())
	}

	iface, err = ifOp.Read(ctx, zone, iface.ID)
	if err != nil {
		return newErrorConflict(o.key, types.ID(0), err.Error())
	}

	mobileGatewayInterface := &iaas.MobileGatewayInterface{}
	copySameNameField(iface, mobileGatewayInterface)
	mobileGatewayInterface.Index = 1 // 1固定
	value.Interfaces = append(value.Interfaces, mobileGatewayInterface)

	putMobileGateway(zone, value)
	return nil
}

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

	var exists bool
	var nicID types.ID
	var interfaces []*iaas.MobileGatewayInterface

	for _, nic := range value.Interfaces {
		if nic != nil && nic.Index == 1 {
			exists = true
			nicID = nic.ID
		} else {
			interfaces = append(interfaces, nic)
		}
	}
	if !exists {
		return newErrorBadRequest(o.key, id, fmt.Sprintf("nic[%d] is not exists", 1))
	}

	ifOp := NewInterfaceOp()
	if err := ifOp.DisconnectFromSwitch(ctx, zone, nicID); err != nil {
		return newErrorConflict(o.key, types.ID(0), err.Error())
	}

	value.Interfaces = interfaces
	putMobileGateway(zone, value)
	return nil
}

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

	dns := ds().Get(o.dnsStoreKey(), zone, id)
	if dns == nil {
		return &iaas.MobileGatewayDNSSetting{
			DNS1: "133.242.0.1",
			DNS2: "133.242.0.2",
		}, nil
	}
	return dns.(*iaas.MobileGatewayDNSSetting), nil
}

// SetDNS is fake implementation
func (o *MobileGatewayOp) SetDNS(ctx context.Context, zone string, id types.ID, param *iaas.MobileGatewayDNSSetting) error {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	ds().Put(o.dnsStoreKey(), zone, id, param)
	return nil
}

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

	routes := ds().Get(o.simRoutesStoreKey(), zone, id)
	if routes == nil {
		return nil, nil
	}

	rs := routes.(*[]*iaas.MobileGatewaySIMRoute)
	var res []*iaas.MobileGatewaySIMRoute
	res = append(res, *rs...)
	return res, nil
}

// SetSIMRoutes is fake implementation
func (o *MobileGatewayOp) SetSIMRoutes(ctx context.Context, zone string, id types.ID, param []*iaas.MobileGatewaySIMRouteParam) error {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	simOp := NewSIMOp()
	var values []*iaas.MobileGatewaySIMRoute
	for _, p := range param {
		sim, err := simOp.Read(ctx, types.StringID(p.ResourceID))
		if err != nil {
			return err
		}
		values = append(values, &iaas.MobileGatewaySIMRoute{
			ResourceID: p.ResourceID,
			Prefix:     p.Prefix,
			ICCID:      sim.ICCID,
		})
	}

	ds().Put(o.simRoutesStoreKey(), zone, id, &values)
	return nil
}

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

	sims := ds().Get(o.simsStoreKey(), zone, id)
	if sims == nil {
		return nil, nil
	}

	ss := sims.(*[]*iaas.MobileGatewaySIMInfo)
	var res []*iaas.MobileGatewaySIMInfo
	res = append(res, *ss...)
	return res, nil
}

// AddSIM is fake implementation
func (o *MobileGatewayOp) AddSIM(ctx context.Context, zone string, id types.ID, param *iaas.MobileGatewayAddSIMRequest) error {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	var sims []*iaas.MobileGatewaySIMInfo
	rawSIMs := ds().Get(o.simsStoreKey(), zone, id)
	if rawSIMs != nil {
		sims = rawSIMs.([]*iaas.MobileGatewaySIMInfo)
		for _, sim := range sims {
			if sim.ResourceID == param.SIMID {
				return newErrorBadRequest(o.key, id, fmt.Sprintf("SIM %s already exists", param.SIMID))
			}
		}
	}

	simOp := NewSIMOp()
	simInfo, err := simOp.Status(context.Background(), types.StringID(param.SIMID))
	if err != nil {
		return err
	}
	sim := &iaas.MobileGatewaySIMInfo{}
	copySameNameField(simInfo, sim)

	sims = append(sims, sim)

	ds().Put(o.simsStoreKey(), zone, id, &sims)
	return nil
}

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

	var updSIMs []*iaas.MobileGatewaySIMInfo
	rawSIMs := ds().Get(o.simsStoreKey(), zone, id)
	if rawSIMs != nil {
		ss := rawSIMs.(*[]*iaas.MobileGatewaySIMInfo)
		for _, sim := range *ss {
			if sim.ResourceID != simID.String() {
				updSIMs = append(updSIMs, sim)
			}
		}
		if len(*ss) != len(updSIMs) {
			ds().Put(o.simsStoreKey(), zone, id, &updSIMs)
			return nil
		}
	}
	return newErrorBadRequest(o.key, id, fmt.Sprintf("SIM %d is not exists", simID))
}

// Logs is fake implementation
func (o *MobileGatewayOp) Logs(ctx context.Context, zone string, id types.ID) ([]*iaas.MobileGatewaySIMLogs, error) {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return nil, err
	}

	return []*iaas.MobileGatewaySIMLogs{
		{
			Date:          time.Now(),
			SessionStatus: "UP",
			ResourceID:    types.ID(1).String(),
		},
	}, nil
}

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

	config := ds().Get(o.trafficConfigStoreKey(), zone, id)
	if config == nil {
		return nil, nil
	}
	return config.(*iaas.MobileGatewayTrafficControl), nil
}

// SetTrafficConfig is fake implementation
func (o *MobileGatewayOp) SetTrafficConfig(ctx context.Context, zone string, id types.ID, param *iaas.MobileGatewayTrafficControl) error {
	_, err := o.Read(ctx, zone, id)
	if err != nil {
		return err
	}

	ds().Put(o.trafficConfigStoreKey(), zone, id, param)
	return nil
}

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

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

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

	return &iaas.MobileGatewayTrafficStatus{
		UplinkBytes:    0,
		DownlinkBytes:  0,
		TrafficShaping: true,
	}, nil
}

// MonitorInterface is fake implementation
func (o *MobileGatewayOp) MonitorInterface(ctx context.Context, zone string, id types.ID, index int, condition *iaas.MonitorCondition) (*iaas.InterfaceActivity, 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.InterfaceActivity{}
	for i := 0; i < 5; i++ {
		res.Values = append(res.Values, &iaas.MonitorInterfaceValue{
			Time:    now.Add(time.Duration(i*-5) * time.Minute),
			Send:    float64(random(1000)),
			Receive: float64(random(1000)),
		})
	}

	return res, nil
}

func (o *MobileGatewayOp) dnsStoreKey() string {
	return o.key + "DNS"
}

func (o *MobileGatewayOp) simRoutesStoreKey() string {
	return o.key + "SIMRoutes"
}

func (o *MobileGatewayOp) simsStoreKey() string {
	return o.key + "SIMs"
}

func (o *MobileGatewayOp) trafficConfigStoreKey() string {
	return o.key + "TrafficConfig"
}
