package ike

import (
	"bytes"
	"crypto"
	"crypto/ecdsa"
	"crypto/rsa"
	"crypto/sha1"
	"crypto/sha256"
	"crypto/sha512"
	"crypto/subtle"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/asn1"
	"errors"
	"fmt"
	"math/big"
	"strings"

	"vocat/internal/vowifi"
)

const (
	authMethodRSASignature     = 1
	authMethodSharedKeyMIC     = 2
	authMethodECDSASHA256P256  = 9
	authMethodECDSASHA384P384  = 10
	authMethodECDSASHA512P521  = 11
	authMethodDigitalSignature = 14
)

var (
	oidSHA256WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 11}
	oidSHA384WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 12}
	oidSHA512WithRSA   = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 1, 13}
	oidECDSAWithSHA256 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 2}
	oidECDSAWithSHA384 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 3}
	oidECDSAWithSHA512 = asn1.ObjectIdentifier{1, 2, 840, 10045, 4, 3, 4}
)

func responderSignedOctets(
	initialResponse []byte,
	initiatorNonce []byte,
	suite negotiatedSuite,
	skpr []byte,
	idr payload,
) ([]byte, error) {
	idHash, err := prf(suite, skpr, idr.Body)
	if err != nil {
		return nil, err
	}
	signed := make([]byte, 0, len(initialResponse)+len(initiatorNonce)+len(idHash))
	signed = append(signed, initialResponse...)
	signed = append(signed, initiatorNonce...)
	signed = append(signed, idHash...)
	return signed, nil
}

func initiatorSignedOctets(
	initialRequest []byte,
	responderNonce []byte,
	suite negotiatedSuite,
	skpi []byte,
	idi payload,
) ([]byte, error) {
	idHash, err := prf(suite, skpi, idi.Body)
	if err != nil {
		return nil, err
	}
	signed := make([]byte, 0, len(initialRequest)+len(responderNonce)+len(idHash))
	signed = append(signed, initialRequest...)
	signed = append(signed, responderNonce...)
	signed = append(signed, idHash...)
	return signed, nil
}

func makeEAPInitiatorAUTH(
	msk []byte,
	initialRequest []byte,
	responderNonce []byte,
	suite negotiatedSuite,
	skpi []byte,
	idi payload,
) (payload, error) {
	signed, err := initiatorSignedOctets(initialRequest, responderNonce, suite, skpi, idi)
	if err != nil {
		return payload{}, err
	}
	paddedKey, err := prf(suite, msk, []byte("Key Pad for IKEv2"))
	if err != nil {
		return payload{}, err
	}
	authValue, err := prf(suite, paddedKey, signed)
	if err != nil {
		return payload{}, err
	}
	body := make([]byte, 4+len(authValue))
	body[0] = authMethodSharedKeyMIC
	copy(body[4:], authValue)
	return payload{Type: payloadAuth, Body: body}, nil
}

func verifyEAPResponderAUTH(
	auth payload,
	msk []byte,
	initialResponse []byte,
	initiatorNonce []byte,
	suite negotiatedSuite,
	skpr []byte,
	idr payload,
) error {
	if len(auth.Body) < 4 || auth.Body[0] != authMethodSharedKeyMIC {
		return errors.New("ike: final responder AUTH does not use the EAP shared-key MIC")
	}
	signed, err := responderSignedOctets(initialResponse, initiatorNonce, suite, skpr, idr)
	if err != nil {
		return err
	}
	paddedKey, err := prf(suite, msk, []byte("Key Pad for IKEv2"))
	if err != nil {
		return err
	}
	expected, err := prf(suite, paddedKey, signed)
	if err != nil {
		return err
	}
	if len(auth.Body[4:]) != len(expected) || subtle.ConstantTimeCompare(auth.Body[4:], expected) != 1 {
		return errors.New("ike: final responder AUTH is invalid")
	}
	return nil
}

func validateInitialResponderAUTH(
	payloads []payload,
	initialResponse []byte,
	initiatorNonce []byte,
	suite negotiatedSuite,
	skpr []byte,
	expectedIDr string,
	serverName string,
	roots *x509.CertPool,
	pinned crypto.PublicKey,
	allowMissing bool,
) (vowifi.ResponderAUTHStatus, payload, error) {
	idrPayloads := payloadsOfType(payloads, payloadIDr)
	authPayloads := payloadsOfType(payloads, payloadAuth)
	if len(authPayloads) == 0 {
		if len(idrPayloads) > 1 {
			return vowifi.ResponderAUTHInvalid, payload{}, errors.New("ike: duplicate responder identity payload")
		}
		if allowMissing {
			if len(idrPayloads) == 1 {
				return vowifi.ResponderAUTHMissing, idrPayloads[0], nil
			}
			return vowifi.ResponderAUTHMissing, payload{}, nil
		}
		return vowifi.ResponderAUTHMissing, payload{}, vowifi.ErrResponderAUTHRequired
	}
	if len(authPayloads) != 1 || len(idrPayloads) != 1 {
		return vowifi.ResponderAUTHInvalid, payload{}, errors.New("ike: responder AUTH requires exactly one IDr and AUTH payload")
	}
	idr := idrPayloads[0]
	auth := authPayloads[0]
	if len(idr.Body) < 4 || len(auth.Body) < 5 {
		return vowifi.ResponderAUTHInvalid, idr, errors.New("ike: responder IDr or AUTH payload is truncated")
	}
	if err := validateFQDNIDr(idr, expectedIDr, "initial ePDG"); err != nil {
		return vowifi.ResponderAUTHInvalid, idr, err
	}
	publicKey := pinned
	if publicKey == nil {
		certificates, err := parseResponderCertificates(payloads)
		if err != nil {
			return vowifi.ResponderAUTHInvalid, idr, err
		}
		if len(certificates) == 0 {
			return vowifi.ResponderAUTHInvalid, idr, errors.New("ike: responder AUTH has no certificate or pinned public key")
		}
		if err := verifyResponderCertificate(certificates, roots, serverName); err != nil {
			return vowifi.ResponderAUTHInvalid, idr, err
		}
		publicKey = certificates[0].PublicKey
	}
	signed, err := responderSignedOctets(initialResponse, initiatorNonce, suite, skpr, idr)
	if err != nil {
		return vowifi.ResponderAUTHInvalid, idr, err
	}
	if err := verifyDigitalAUTH(publicKey, auth.Body[0], auth.Body[4:], signed); err != nil {
		return vowifi.ResponderAUTHInvalid, idr, fmt.Errorf("ike: invalid responder AUTH: %w", err)
	}
	return vowifi.ResponderAUTHVerified, idr, nil
}

func validateFQDNIDr(idr payload, expectedIDr string, label string) error {
	if len(idr.Body) < 4 {
		return errors.New("ike: responder identity is truncated")
	}
	identityType := idr.Body[0]
	identity := strings.TrimSpace(string(idr.Body[4:]))
	if identityType != 2 {
		return fmt.Errorf("ike: %s IDr must use ID_FQDN, got type %d", label, identityType)
	}
	if identity == "" {
		return fmt.Errorf("ike: %s IDr is empty", label)
	}
	if expectedIDr != "" && !strings.EqualFold(strings.TrimSuffix(identity, "."), strings.TrimSuffix(expectedIDr, ".")) {
		return fmt.Errorf("ike: %s IDr %q does not match %q", label, identity, expectedIDr)
	}
	return nil
}

func parseResponderCertificates(payloads []payload) ([]*x509.Certificate, error) {
	var certificates []*x509.Certificate
	for _, item := range payloadsOfType(payloads, payloadCert) {
		if len(item.Body) < 2 {
			return nil, errors.New("ike: responder certificate payload is truncated")
		}
		if item.Body[0] != 4 {
			return nil, fmt.Errorf("ike: unsupported responder certificate encoding %d", item.Body[0])
		}
		certificate, err := x509.ParseCertificate(item.Body[1:])
		if err != nil {
			return nil, fmt.Errorf("ike: parse responder certificate: %w", err)
		}
		certificates = append(certificates, certificate)
	}
	return certificates, nil
}

func verifyResponderCertificate(certificates []*x509.Certificate, roots *x509.CertPool, serverName string) error {
	if len(certificates) == 0 {
		return errors.New("ike: no responder certificate")
	}
	if roots == nil {
		var err error
		roots, err = x509.SystemCertPool()
		if err != nil {
			return fmt.Errorf("ike: load system certificate roots: %w", err)
		}
	}
	intermediates := x509.NewCertPool()
	for _, certificate := range certificates[1:] {
		intermediates.AddCert(certificate)
	}
	options := x509.VerifyOptions{
		Roots:         roots,
		Intermediates: intermediates,
		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
		DNSName:       strings.TrimSuffix(serverName, "."),
	}
	if _, err := certificates[0].Verify(options); err != nil {
		return fmt.Errorf("ike: verify responder certificate: %w", err)
	}
	return nil
}

func verifyDigitalAUTH(publicKey crypto.PublicKey, method uint8, signature, signed []byte) error {
	switch method {
	case authMethodRSASignature:
		key, ok := publicKey.(*rsa.PublicKey)
		if !ok {
			return errors.New("RSA AUTH used with a non-RSA public key")
		}
		digest := sha1.Sum(signed)
		return rsa.VerifyPKCS1v15(key, crypto.SHA1, digest[:], signature)
	case authMethodECDSASHA256P256:
		return verifyRawECDSA(publicKey, crypto.SHA256, signature, signed)
	case authMethodECDSASHA384P384:
		return verifyRawECDSA(publicKey, crypto.SHA384, signature, signed)
	case authMethodECDSASHA512P521:
		return verifyRawECDSA(publicKey, crypto.SHA512, signature, signed)
	case authMethodDigitalSignature:
		return verifyGenericSignature(publicKey, signature, signed)
	default:
		return fmt.Errorf("unsupported responder AUTH method %d", method)
	}
}

func verifyRawECDSA(publicKey crypto.PublicKey, algorithm crypto.Hash, signature, signed []byte) error {
	key, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return errors.New("ECDSA AUTH used with a non-ECDSA public key")
	}
	size := (key.Curve.Params().BitSize + 7) / 8
	if len(signature) != size*2 {
		return fmt.Errorf("ECDSA signature length %d does not match curve size %d", len(signature), size)
	}
	digest, err := hashSignedOctets(algorithm, signed)
	if err != nil {
		return err
	}
	r := new(big.Int).SetBytes(signature[:size])
	s := new(big.Int).SetBytes(signature[size:])
	if !ecdsa.Verify(key, digest, r, s) {
		return errors.New("ECDSA signature verification failed")
	}
	return nil
}

func verifyGenericSignature(publicKey crypto.PublicKey, encoded, signed []byte) error {
	var algorithm pkix.AlgorithmIdentifier
	rest, err := asn1.Unmarshal(encoded, &algorithm)
	if err != nil || len(rest) == 0 {
		return errors.New("generic digital signature has an invalid AlgorithmIdentifier")
	}
	var hashAlgorithm crypto.Hash
	var isRSA bool
	switch {
	case algorithm.Algorithm.Equal(oidSHA256WithRSA):
		hashAlgorithm, isRSA = crypto.SHA256, true
	case algorithm.Algorithm.Equal(oidSHA384WithRSA):
		hashAlgorithm, isRSA = crypto.SHA384, true
	case algorithm.Algorithm.Equal(oidSHA512WithRSA):
		hashAlgorithm, isRSA = crypto.SHA512, true
	case algorithm.Algorithm.Equal(oidECDSAWithSHA256):
		hashAlgorithm = crypto.SHA256
	case algorithm.Algorithm.Equal(oidECDSAWithSHA384):
		hashAlgorithm = crypto.SHA384
	case algorithm.Algorithm.Equal(oidECDSAWithSHA512):
		hashAlgorithm = crypto.SHA512
	default:
		return fmt.Errorf("unsupported generic signature algorithm %s", algorithm.Algorithm.String())
	}
	digest, err := hashSignedOctets(hashAlgorithm, signed)
	if err != nil {
		return err
	}
	if isRSA {
		key, ok := publicKey.(*rsa.PublicKey)
		if !ok {
			return errors.New("RSA signature used with a non-RSA public key")
		}
		return rsa.VerifyPKCS1v15(key, hashAlgorithm, digest, rest)
	}
	key, ok := publicKey.(*ecdsa.PublicKey)
	if !ok {
		return errors.New("ECDSA signature used with a non-ECDSA public key")
	}
	if !ecdsa.VerifyASN1(key, digest, rest) {
		return errors.New("ECDSA generic signature verification failed")
	}
	return nil
}

func hashSignedOctets(algorithm crypto.Hash, signed []byte) ([]byte, error) {
	switch algorithm {
	case crypto.SHA1:
		sum := sha1.Sum(signed)
		return sum[:], nil
	case crypto.SHA256:
		sum := sha256.Sum256(signed)
		return sum[:], nil
	case crypto.SHA384:
		sum := sha512.Sum384(signed)
		return sum[:], nil
	case crypto.SHA512:
		sum := sha512.Sum512(signed)
		return sum[:], nil
	default:
		return nil, fmt.Errorf("unsupported signature hash %v", algorithm)
	}
}

func equalPublicKeys(first, second crypto.PublicKey) bool {
	firstDER, firstErr := x509.MarshalPKIXPublicKey(first)
	secondDER, secondErr := x509.MarshalPKIXPublicKey(second)
	return firstErr == nil && secondErr == nil && bytes.Equal(firstDER, secondDER)
}
