package installer

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/url"
)

type VPNAPIClient interface {
	GenerateAuthKey(username string) (string, error)
	ExpireKey(username, key string) error
	ExpireNode(username, node string) error
	RemoveNode(username, node string) error
	GetNodeIP(username, node string) (net.IP, error)
}

type headscaleAPIClient struct {
	c       *http.Client
	apiAddr string
}

func NewHeadscaleAPIClient(apiAddr string) VPNAPIClient {
	return &headscaleAPIClient{
		&http.Client{},
		apiAddr,
	}
}

func (g *headscaleAPIClient) GenerateAuthKey(username string) (string, error) {
	resp, err := http.Post(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username), "application/json", nil)
	if err != nil {
		return "", err
	}
	var buf bytes.Buffer
	io.Copy(&buf, resp.Body)
	if resp.StatusCode != http.StatusOK {
		return "", errors.New(buf.String())
	}
	return buf.String(), nil
}

type expirePreAuthKeyReq struct {
	AuthKey string `json:"authKey"`
}

func (g *headscaleAPIClient) ExpireKey(username, key string) error {
	addr, err := url.Parse(fmt.Sprintf("%s/user/%s/preauthkey", g.apiAddr, username))
	if err != nil {
		return err
	}
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(expirePreAuthKeyReq{key}); err != nil {
		return err
	}
	resp, err := g.c.Do(&http.Request{
		URL:    addr,
		Method: http.MethodDelete,
		Body:   io.NopCloser(&buf),
	})
	if err != nil {
		return err
	}
	if resp.StatusCode == http.StatusOK {
		return nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return ErrorNotFound
	}
	return nil
}

func (g *headscaleAPIClient) ExpireNode(username, node string) error {
	resp, err := g.c.Post(
		fmt.Sprintf("%s/user/%s/node/%s/expire", g.apiAddr, username, node),
		"text/plain",
		nil,
	)
	if err != nil {
		return err
	}
	if resp.StatusCode == http.StatusOK {
		return nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return ErrorNotFound
	}
	return nil
}

func (g *headscaleAPIClient) RemoveNode(username, node string) error {
	addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s", g.apiAddr, username, node))
	if err != nil {
		return err
	}
	resp, err := g.c.Do(&http.Request{
		URL:    addr,
		Method: http.MethodDelete,
		Body:   nil,
	})
	if err != nil {
		return err
	}
	if resp.StatusCode == http.StatusOK {
		return nil
	}
	if resp.StatusCode == http.StatusNotFound {
		return ErrorNotFound
	}
	return nil
}

func (g *headscaleAPIClient) GetNodeIP(username, node string) (net.IP, error) {
	addr, err := url.Parse(fmt.Sprintf("%s/user/%s/node/%s/ip", g.apiAddr, username, node))
	if err != nil {
		return nil, err
	}
	resp, err := g.c.Do(&http.Request{
		URL:    addr,
		Method: http.MethodGet,
		Body:   nil,
	})
	if err != nil {
		return nil, err
	}
	var buf bytes.Buffer
	if _, err := io.Copy(&buf, resp.Body); err != nil {
		return nil, err
	}
	bufS := buf.String()
	if resp.StatusCode == http.StatusNotFound {
		return nil, ErrorNotFound
	}
	if resp.StatusCode != http.StatusOK {
		return nil, errors.New(bufS)
	}
	ip := net.ParseIP(bufS)
	if ip == nil {
		return nil, fmt.Errorf("invalid ip")
	}
	return ip, nil
}
