package ldapserver

import (
	"errors"
	"fmt"
	"net"

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

func HandleModifyRequest(req *ber.Packet, boundDN string, server *Server, conn net.Conn) error {
	if boundDN == "" {
		return ldap.NewError(ldap.LDAPResultInsufficientAccessRights, errors.New("anonymous Write denied"))
	}
	modReq, err := parseModifyRequest(req)
	if err != nil {
		return err
	}

	logger.V(1).Info("Parsed Modification", "request", dumpModRequest(modReq))

	fnNames := []string{}
	for k := range server.ModifyFns {
		fnNames = append(fnNames, k)
	}
	fn := routeFunc(modReq.DN, fnNames)
	var modifier Modifier
	if modifier = server.ModifyFns[fn]; modifier == nil {
		if fn == "" {
			err = fmt.Errorf("no suitable handler found for dn: '%s'", modReq.DN)
		} else {
			err = fmt.Errorf("handler '%s' does not support modify", fn)
		}
		return ldap.NewError(ldap.LDAPResultUnwillingToPerform, err)
	}
	code, err := modifier.Modify(boundDN, modReq, conn)
	return ldap.NewError(uint16(code), err)
}

func dumpModRequest(mr *ldap.ModifyRequest) string {
	str := fmt.Sprintf("dn: %s\n", mr.DN)
	for _, change := range mr.Changes {
		str += fmt.Sprintf("op: %d\n attr: %s values: %v\n", change.Operation, change.Modification.Type, change.Modification.Vals)
	}
	return str
}

func parseModifyRequest(req *ber.Packet) (*ldap.ModifyRequest, error) {
	modReq := ldap.ModifyRequest{}
	// LDAP Modify requests have 2 Elements (DN and AttributeList)
	if len(req.Children) != 2 {
		return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("invalid modify request"))
	}

	dn, ok := req.Children[0].Value.(string)
	if !ok {
		return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding entry DN"))
	}

	_, err := ldap.ParseDN(dn)
	if err != nil {
		return nil, ldap.NewError(ldap.LDAPResultProtocolError, err)
	}
	modReq.DN = dn

	ml, err := parseModList(req.Children[1])
	if err != nil {
		return nil, err
	}
	modReq.Changes = ml

	return &modReq, nil
}

func parseModList(req *ber.Packet) ([]ldap.Change, error) {
	var changes []ldap.Change

	if req.ClassType != ber.ClassUniversal || req.TagType != ber.TypeConstructed || req.Tag != ber.TagSequence {
		return nil, ldap.NewError(ldap.LDAPResultProtocolError, errors.New("error decoding Changes List"))
	}

	for _, c := range req.Children {
		var change ldap.Change
		switch c.Children[0].Data.Bytes()[0] {
		default:
			return nil, ldap.NewError(ldap.LDAPResultDecodingError, errors.New("invalid change operation"))
		case ldap.AddAttribute:
			change.Operation = ldap.AddAttribute
			logger.V(1).Info("op=add")
		case ldap.ReplaceAttribute:
			change.Operation = ldap.ReplaceAttribute
			logger.V(1).Info("op=replace")
		case ldap.DeleteAttribute:
			change.Operation = ldap.DeleteAttribute
			logger.V(1).Info("op=delete")
		}
		attr, err := parseAttribute(c.Children[1], true)
		if err != nil {
			return nil, err
		}
		change.Modification = ldap.PartialAttribute{Type: attr.Type, Vals: attr.Vals}
		changes = append(changes, change)

	}
	return changes, nil
}
