package pht

import (
	"context"
	"errors"
	"fmt"
	"io"
	"net"
	"net/http"
	"net/http/httputil"
	"strconv"
	"strings"

	"github.com/go-gost/core/logger"
)

type Client struct {
	Host          string
	Client        *http.Client
	AuthorizePath string
	PushPath      string
	PullPath      string
	TLSEnabled    bool
	Header        http.Header
	Logger        logger.Logger
}

func (c *Client) Dial(ctx context.Context, addr string) (net.Conn, error) {
	raddr, err := net.ResolveTCPAddr("tcp", addr)
	if err != nil {
		c.Logger.Error(err)
		return nil, err
	}

	if c.Host != "" {
		addr = net.JoinHostPort(c.Host, strconv.Itoa(raddr.Port))
	}

	token, err := c.authorize(ctx, addr)
	if err != nil {
		c.Logger.Error(err)
		return nil, err
	}

	cn := &clientConn{
		client:     c.Client,
		header:     c.Header,
		rxc:        make(chan []byte, 128),
		closed:     make(chan struct{}),
		localAddr:  &net.TCPAddr{},
		remoteAddr: raddr,
		logger:     c.Logger,
	}

	scheme := "http"
	if c.TLSEnabled {
		scheme = "https"
	}
	cn.pushURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PushPath, token)
	cn.pullURL = fmt.Sprintf("%s://%s%s?token=%s", scheme, addr, c.PullPath, token)

	go cn.readLoop()

	return cn, nil
}

func (c *Client) authorize(ctx context.Context, addr string) (token string, err error) {
	var url string
	if c.TLSEnabled {
		url = fmt.Sprintf("https://%s%s", addr, c.AuthorizePath)
	} else {
		url = fmt.Sprintf("http://%s%s", addr, c.AuthorizePath)
	}
	r, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return
	}

	// Inject custom headers
	if c.Header != nil {
		for k, values := range c.Header {
			for _, v := range values {
				r.Header.Add(k, v)
			}
		}
	}

	if c.Logger.IsLevelEnabled(logger.TraceLevel) {
		dump, _ := httputil.DumpRequest(r, false)
		c.Logger.Trace(string(dump))
	} else if c.Logger.IsLevelEnabled(logger.DebugLevel) {
		c.Logger.Debugf("%s %s", r.Method, r.URL)
	}

	resp, err := c.Client.Do(r)
	if err != nil {
		return
	}
	defer resp.Body.Close()

	if c.Logger.IsLevelEnabled(logger.TraceLevel) {
		dump, _ := httputil.DumpResponse(resp, false)
		c.Logger.Trace(string(dump))
	}

	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return
	}

	if strings.HasPrefix(string(data), "token=") {
		token = strings.TrimPrefix(string(data), "token=")
	}
	if token == "" {
		err = errors.New("authorize failed")
	}
	return
}
