// Package headers contains various RTSP headers.
package headers

import (
	"fmt"
	"strings"

	"github.com/bluenviron/gortsplib/v5/pkg/base"
)

// AuthMethod is an authentication method.
type AuthMethod int

// authentication methods.
const (
	AuthMethodBasic AuthMethod = iota
	AuthMethodDigest
)

// AuthAlgorithm is a digest algorithm.
type AuthAlgorithm int

// digest algorithms.
const (
	AuthAlgorithmMD5 AuthAlgorithm = iota
	AuthAlgorithmSHA256
)

func parseAuthAlgorithm(v string) (AuthAlgorithm, error) {
	switch {
	case strings.ToLower(v) == "md5":
		return AuthAlgorithmMD5, nil

	case strings.ToLower(v) == "sha-256":
		return AuthAlgorithmSHA256, nil

	default:
		return 0, fmt.Errorf("unrecognized algorithm: %v", v)
	}
}

// Authenticate is a WWW-Authenticate header.
type Authenticate struct {
	// authentication method
	Method AuthMethod

	// realm
	Realm string

	//
	// Digest authentication fields
	//

	// nonce
	Nonce string

	// opaque
	Opaque *string

	// stale
	Stale *string

	// algorithm
	Algorithm *AuthAlgorithm
}

// Unmarshal decodes a WWW-Authenticate header.
func (h *Authenticate) Unmarshal(v base.HeaderValue) error {
	if len(v) == 0 {
		return fmt.Errorf("value not provided")
	}

	if len(v) > 1 {
		return fmt.Errorf("value provided multiple times (%v)", v)
	}

	v0 := v[0]

	i := strings.IndexByte(v0, ' ')
	if i < 0 {
		return fmt.Errorf("unable to split between method and keys (%v)", v0)
	}
	method, v0 := v0[:i], v0[i+1:]

	switch method {
	case "Basic":
		h.Method = AuthMethodBasic

	case "Digest":
		h.Method = AuthMethodDigest

	default:
		return fmt.Errorf("invalid method (%s)", method)
	}

	if h.Method == AuthMethodBasic {
		kvs, err := keyValParse(v0, ',')
		if err != nil {
			return err
		}

		realmReceived := false

		for k, rv := range kvs {
			v := rv

			if k == "realm" {
				h.Realm = v
				realmReceived = true
			}
		}

		if !realmReceived {
			return fmt.Errorf("realm is missing")
		}
	} else { // digest
		kvs, err := keyValParse(v0, ',')
		if err != nil {
			return err
		}

		realmReceived := false
		nonceReceived := false

		for k, rv := range kvs {
			v := rv

			switch k {
			case "realm":
				h.Realm = v
				realmReceived = true

			case "nonce":
				h.Nonce = v
				nonceReceived = true

			case "opaque":
				h.Opaque = &v

			case "stale":
				h.Stale = &v

			case "algorithm":
				var a AuthAlgorithm
				a, err = parseAuthAlgorithm(v)
				if err != nil {
					return err
				}
				h.Algorithm = &a
			}
		}

		if !realmReceived || !nonceReceived {
			return fmt.Errorf("one or more digest fields are missing")
		}
	}

	return nil
}

// Marshal encodes a WWW-Authenticate header.
func (h Authenticate) Marshal() base.HeaderValue {
	if h.Method == AuthMethodBasic {
		return base.HeaderValue{"Basic " +
			"realm=\"" + h.Realm + "\""}
	}

	ret := "Digest realm=\"" + h.Realm + "\", nonce=\"" + h.Nonce + "\""

	if h.Opaque != nil {
		ret += ", opaque=\"" + *h.Opaque + "\""
	}

	if h.Stale != nil {
		ret += ", stale=\"" + *h.Stale + "\""
	}

	if h.Algorithm != nil {
		if *h.Algorithm == AuthAlgorithmMD5 {
			ret += ", algorithm=\"MD5\""
		} else {
			ret += ", algorithm=\"SHA-256\""
		}
	}

	return base.HeaderValue{ret}
}
