package ldap

import (
	"errors"
	"fmt"

	ber "github.com/go-asn1-ber/asn1-ber"
)

// ExtendedRequest represents an extended request to send to the server
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedRequest struct {
	// ExtendedRequest ::= [APPLICATION 23] SEQUENCE {
	// 	requestName      [0] LDAPOID,
	// 	requestValue     [1] OCTET STRING OPTIONAL }

	Name     string
	Value    *ber.Packet
	Controls []Control
}

// NewExtendedRequest returns a new ExtendedRequest. The value can be
// nil depending on the type of request
func NewExtendedRequest(name string, value *ber.Packet) *ExtendedRequest {
	return &ExtendedRequest{
		Name:  name,
		Value: value,
	}
}

func (er ExtendedRequest) appendTo(envelope *ber.Packet) error {
	pkt := ber.Encode(ber.ClassApplication, ber.TypeConstructed, ApplicationExtendedRequest, nil, "Extended Request")
	pkt.AppendChild(ber.NewString(ber.ClassContext, ber.TypePrimitive, ber.TagEOC, er.Name, "Extended Request Name"))
	if er.Value != nil {
		pkt.AppendChild(er.Value)
	}
	envelope.AppendChild(pkt)
	if len(er.Controls) > 0 {
		envelope.AppendChild(encodeControls(er.Controls))
	}
	return nil
}

// ExtendedResponse represents the response from the directory server
// after sending an extended request
// See: https://www.rfc-editor.org/rfc/rfc4511#section-4.12
type ExtendedResponse struct {
	// ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
	//   COMPONENTS OF LDAPResult,
	//   responseName     [10] LDAPOID OPTIONAL,
	//   responseValue    [11] OCTET STRING OPTIONAL }

	Name     string
	Value    *ber.Packet
	Controls []Control
}

// Extended performs an extended request. The resulting
// ExtendedResponse may return a value in the form of a *ber.Packet
func (l *Conn) Extended(er *ExtendedRequest) (*ExtendedResponse, error) {
	if er == nil {
		return nil, NewError(ErrorNetwork, errors.New("ExtendedRequest cannot be nil"))
	}

	msgCtx, err := l.doRequest(er)
	if err != nil {
		return nil, err
	}
	defer l.finishMessage(msgCtx)

	packet, err := l.readPacket(msgCtx)
	if err != nil {
		return nil, err
	}
	if err = GetLDAPError(packet); err != nil {
		return nil, err
	}

	extResp := packet.Children[1]
	if len(extResp.Children) < 3 {
		return nil, fmt.Errorf(
			"ldap: malformed extended response: expected at least 3 children, got %d",
			len(packet.Children),
		)
	}

	response := &ExtendedResponse{
		Controls: make([]Control, 0),
	}

	for _, child := range extResp.Children {
		switch child.Tag {
		case ber.TagEnumerated:
			response.Name = child.Data.String()
		case ber.TagEmbeddedPDV:
			response.Value = child
		}
	}

	if len(packet.Children) == 3 {
		for _, child := range packet.Children[2].Children {
			decodedChild, decodeErr := DecodeControl(child)
			if decodeErr != nil {
				return nil, fmt.Errorf("failed to decode child control: %s", decodeErr)
			}
			response.Controls = append(response.Controls, decodedChild)
		}
	}

	return response, nil
}
