/*
Copyright 2018 The Kubernetes Authors All rights reserved.

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 tunnel

import (
	"path/filepath"
	"time"

	"context"
	"fmt"

	"github.com/docker/machine/libmachine"
	typed_core "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/klog/v2"
	"k8s.io/minikube/pkg/minikube/config"
	"k8s.io/minikube/pkg/minikube/localpath"
)

// Manager can create, start and cleanup a tunnel
// It keeps track of created tunnels for multiple vms so that it can cleanup
// after unclean shutdowns.
type Manager struct {
	delay    time.Duration
	registry *persistentRegistry
	router   router
}

// stateCheckInterval defines how frequently the cluster and route states are checked
const stateCheckInterval = 5 * time.Second

// RegistryPath returns the path to the runnel registry file
func RegistryPath() string {
	return filepath.Join(localpath.MiniPath(), "tunnels.json")
}

// NewManager creates a new Manager
func NewManager() *Manager {
	return &Manager{
		delay: stateCheckInterval,
		registry: &persistentRegistry{
			path: RegistryPath(),
		},
		router: &osRouter{},
	}
}

// StartTunnel starts the tunnel
func (mgr *Manager) StartTunnel(ctx context.Context, machineName string, machineAPI libmachine.API, configLoader config.Loader, v1Core typed_core.CoreV1Interface) (done chan bool, err error) {
	tunnel, err := newTunnel(machineName, machineAPI, configLoader, v1Core, mgr.registry, mgr.router)
	if err != nil {
		return nil, fmt.Errorf("error creating tunnel: %s", err)
	}
	return mgr.startTunnel(ctx, tunnel)

}
func (mgr *Manager) startTunnel(ctx context.Context, tunnel controller) (done chan bool, err error) {
	klog.Info("Setting up tunnel...")

	ready := make(chan bool, 1)
	check := make(chan bool, 1)
	done = make(chan bool, 1)

	// simulating Ctrl+C so that we can cancel the tunnel programmatically too
	go mgr.timerLoop(ready, check)
	go mgr.run(ctx, tunnel, ready, check, done)

	klog.Info("Started minikube tunnel.")
	return
}

func (mgr *Manager) timerLoop(ready, check chan bool) {
	for {
		klog.V(4).Info("waiting for tunnel to be ready for next check")
		<-ready
		klog.V(4).Infof("sleep for %s", mgr.delay)
		time.Sleep(mgr.delay)
		check <- true
	}
}

func (mgr *Manager) run(ctx context.Context, t controller, ready, check, done chan bool) {
	defer func() {
		done <- true
	}()
	ready <- true
	for {
		select {
		case <-ctx.Done():
			mgr.cleanup(t)
			return
		case <-check:
			klog.V(4).Info("check received")
			select {
			case <-ctx.Done():
				mgr.cleanup(t)
				return
			default:
			}
			status := t.update()
			klog.V(4).Infof("minikube status: %s", status)
			if status.MinikubeState != Running {
				klog.Infof("minikube status: %s, cleaning up and quitting...", status.MinikubeState)
				mgr.cleanup(t)
				return
			}
			ready <- true
		}
	}
}

func (mgr *Manager) cleanup(t controller) {
	t.cleanup()
}

// CleanupNotRunningTunnels cleans up tunnels that are not running
func (mgr *Manager) CleanupNotRunningTunnels() error {
	tunnels, err := mgr.registry.List()
	if err != nil {
		return fmt.Errorf("error listing tunnels from registry: %s", err)
	}

	for _, tunnel := range tunnels {
		isRunning, err := checkIfRunning(tunnel.Pid)
		klog.Infof("%v is running: %t", tunnel, isRunning)
		if err != nil {
			return fmt.Errorf("error checking if tunnel is running: %s", err)
		}
		if !isRunning {
			err = mgr.router.Cleanup(tunnel.Route)
			if err != nil {
				return err
			}
			err = mgr.registry.Remove(tunnel.Route)
			if err != nil {
				return err
			}
		}
	}
	return nil
}
