package state

import (
	"context"
	"fmt"
	"sync"

	"google.golang.org/grpc"

	ipb "code.hackerspace.pl/hscloud/go/proto/infra"
	confpb "code.hackerspace.pl/hscloud/go/svc/topo/proto/config"

	"code.hackerspace.pl/hscloud/go/pki"
)

type SwitchportState struct {
	Proto *ipb.SwitchPort
}

type SwitchState struct {
	Name  string
	Ports []*SwitchportState
	Stub  ipb.SwitchControlClient
}

func (s *SwitchState) Fetch(ctx context.Context) error {
	req := ipb.GetPortsRequest{}
	res, err := s.Stub.GetPorts(ctx, &req)
	if err != nil {
		return fmt.Errorf("GetPorts: %v", err)
	}
	s.Ports = make([]*SwitchportState, len(res.Ports))
	for i, port := range res.Ports {
		s.Ports[i] = &SwitchportState{port}
	}
	return nil
}

type StateManager struct {
	Switches map[string]*SwitchState
	Conns    map[string]*grpc.ClientConn
	Mu       sync.RWMutex
}

func NewManager() *StateManager {
	return &StateManager{
		Switches: make(map[string]*SwitchState),
		Conns:    make(map[string]*grpc.ClientConn),
	}
}

func (s *StateManager) FetchState(ctx context.Context, conf *confpb.Config) error {
	s.Mu.Lock()
	defer s.Mu.Unlock()
	for _, sw := range conf.Switch {
		conn, ok := s.Conns[sw.ControlAddress]
		if !ok {
			var err error
			conn, err = grpc.Dial(sw.ControlAddress, pki.WithClientHSPKI())
			if err != nil {
				return fmt.Errorf("when connecting to switch %s: %v", sw.Name, err)
			}
			s.Conns[sw.ControlAddress] = conn
		}

		s.Switches[sw.Name] = &SwitchState{
			Name: sw.Name,
			Stub: ipb.NewSwitchControlClient(conn),
		}
		err := s.Switches[sw.Name].Fetch(ctx)
		if err != nil {
			return fmt.Errorf("%q.Fetch: %v", sw.Name, err)
		}
	}
	return nil
}
