package kube

import (
	"bytes"
	"fmt"
	"net"
	"strconv"

	"k8s.io/kubernetes/pkg/client/unversioned/portforward"
	"k8s.io/kubernetes/pkg/client/unversioned/remotecommand"
)

// Tunnel describes a ssh-like tunnel to a kubernetes pod
type Tunnel struct {
	Local    int
	Remote   int
	stopChan chan struct{}
}

// Close disconnects a tunnel connection
func (t *Tunnel) Close() {
	close(t.stopChan)
}

// ForwardPort opens a tunnel to a kubernetes pod
func (c *Client) ForwardPort(namespace, podName string, remote int) (*Tunnel, error) {
	client, err := c.Client()
	if err != nil {
		return nil, err
	}

	config, err := c.ClientConfig()
	if err != nil {
		return nil, err
	}

	// Build a url to the portforward endpoing
	// example: http://localhost:8080/api/v1/namespaces/helm/pods/tiller-rc-9itlq/portforward
	u := client.RESTClient.Post().
		Resource("pods").
		Namespace(namespace).
		Name(podName).
		SubResource("portforward").URL()

	dialer, err := remotecommand.NewExecutor(config, "POST", u)
	if err != nil {
		return nil, err
	}

	local, err := getAvailablePort()
	if err != nil {
		return nil, err
	}

	t := &Tunnel{
		Local:    local,
		Remote:   remote,
		stopChan: make(chan struct{}, 1),
	}

	ports := []string{fmt.Sprintf("%d:%d", local, remote)}

	var b bytes.Buffer
	pf, err := portforward.New(dialer, ports, t.stopChan, &b, &b)
	if err != nil {
		return nil, err
	}

	go func() {
		if err := pf.ForwardPorts(); err != nil {
			fmt.Printf("Error forwarding ports: %v\n", err)
		}
	}()

	// wait for listeners to start
	<-pf.Ready

	return t, nil
}

func getAvailablePort() (int, error) {
	l, err := net.Listen("tcp", ":0")
	if err != nil {
		return 0, err
	}
	defer l.Close()

	_, p, err := net.SplitHostPort(l.Addr().String())
	port, err := strconv.Atoi(p)
	if err != nil {
		return 0, err
	}
	return port, err
}