package internal

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/go-acme/lego/v5/internal/errutils"
)

const authPath = "KasAuth.php"

type token string

const tokenKey token = "token"

// Identifier generates credential tokens.
type Identifier struct {
	login    string
	password string

	BaseURL    *url.URL
	HTTPClient *http.Client
}

// NewIdentifier creates a new Identifier.
func NewIdentifier(login, password string) *Identifier {
	baseURL, _ := url.Parse(defaultBaseURL)

	return &Identifier{
		login:      login,
		password:   password,
		BaseURL:    baseURL,
		HTTPClient: &http.Client{Timeout: 10 * time.Second},
	}
}

// Authentication Creates a credential token.
// - sessionLifetime: Validity of the token in seconds.
// - sessionUpdateLifetime: with `true` the session is extended with every request.
func (c *Identifier) Authentication(ctx context.Context, sessionLifetime int, sessionUpdateLifetime bool) (string, error) {
	sul := "N"
	if sessionUpdateLifetime {
		sul = "Y"
	}

	ar := AuthRequest{
		Login:                 c.login,
		AuthData:              c.password,
		AuthType:              "plain",
		SessionLifetime:       sessionLifetime,
		SessionUpdateLifetime: sul,
	}

	body, err := json.Marshal(ar)
	if err != nil {
		return "", fmt.Errorf("failed to create request JSON body: %w", err)
	}

	payload := []byte(strings.TrimSpace(fmt.Sprintf(kasAuthEnvelope, body)))

	endpoint := c.BaseURL.JoinPath(authPath)

	req, err := http.NewRequestWithContext(ctx, http.MethodPost, endpoint.String(), bytes.NewReader(payload))
	if err != nil {
		return "", fmt.Errorf("unable to create request: %w", err)
	}

	resp, err := c.HTTPClient.Do(req)
	if err != nil {
		return "", errutils.NewHTTPDoError(req, err)
	}

	defer func() { _ = resp.Body.Close() }()

	if resp.StatusCode != http.StatusOK {
		return "", errutils.NewUnexpectedResponseStatusCodeError(req, resp)
	}

	envlp, err := decodeXML[KasAuthEnvelope](resp.Body)
	if err != nil {
		return "", err
	}

	if envlp.Body.Fault != nil {
		return "", envlp.Body.Fault
	}

	return envlp.Body.KasAuthResponse.Return.Text, nil
}

func WithContext(ctx context.Context, credential string) context.Context {
	return context.WithValue(ctx, tokenKey, credential)
}

func getToken(ctx context.Context) string {
	credential, ok := ctx.Value(tokenKey).(string)
	if !ok {
		return ""
	}

	return credential
}
