package pia

import (
	"bytes"
	"context"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"mime"
	"net"
	"net/http"
	"strings"
)

func readLimitedBody(body io.Reader, limit int64) ([]byte, error) {
	raw, err := io.ReadAll(io.LimitReader(body, limit+1))
	if err != nil {
		return nil, err
	}
	if int64(len(raw)) > limit {
		return nil, fmt.Errorf("response exceeds %d bytes", limit)
	}
	return raw, nil
}

func expectedContentType(header string, accepted ...string) bool {
	mediaType, _, err := mime.ParseMediaType(header)
	if err != nil {
		return false
	}
	for _, candidate := range accepted {
		if strings.EqualFold(mediaType, candidate) {
			return true
		}
	}
	return false
}

func noRedirect(_ *http.Request, _ []*http.Request) error {
	return errors.New("redirects are disabled for this request")
}

func decodeSingleJSON(raw []byte, target any) error {
	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.UseNumber()
	if err := decoder.Decode(target); err != nil {
		return err
	}
	var extra any
	if err := decoder.Decode(&extra); !errors.Is(err, io.EOF) {
		if err == nil {
			return errors.New("multiple JSON values are not allowed")
		}
		return err
	}
	return nil
}

func classifyNetworkError(ctx context.Context, fallback, message string, err error) error {
	cause := redactNetErr(err)
	if errors.Is(ctx.Err(), context.Canceled) || errors.Is(err, context.Canceled) {
		return WrapError(CodeCancelled, "The operation was cancelled.", cause)
	}
	if errors.Is(ctx.Err(), context.DeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
		return WrapError(CodeTimeout, "The network request timed out.", cause)
	}
	var netErr net.Error
	if errors.As(err, &netErr) && netErr.Timeout() {
		return WrapError(CodeTimeout, "The network request timed out.", cause)
	}
	var unknownAuthority x509.UnknownAuthorityError
	var hostnameError x509.HostnameError
	var invalidCertificate x509.CertificateInvalidError
	if errors.As(err, &unknownAuthority) || errors.As(err, &hostnameError) || errors.As(err, &invalidCertificate) {
		return WrapError(CodeTLSValidation, "PIA's server identity could not be verified.", cause)
	}
	return WrapError(fallback, message, cause)
}

type redactedCause struct{ kind string }

func (e redactedCause) Error() string { return e.kind }

func redactNetErr(err error) error {
	if err == nil {
		return nil
	}
	return redactedCause{kind: "network error"}
}

func containsSecret(s string, secrets ...string) bool {
	for _, secret := range secrets {
		if secret != "" && strings.Contains(s, secret) {
			return true
		}
	}
	return false
}
