package layoutroutingsecurityv1

import (
	"bytes"
	"encoding/binary"
	"fmt"
)

// ParseCanonicalView decodes one exact v1 canonical routing-security view.
// It rejects truncated, alternate, unordered, or otherwise noncanonical byte
// streams. The returned View owns its own strings, slices, and parent keys.
func ParseCanonicalView(raw []byte) (View, error) {
	if len(raw) == 0 || len(raw) > MaxCanonicalViewBytes {
		return View{}, fmt.Errorf(
			"LayoutRoutingSecurityView canonical bytes must be 1..%d bytes",
			MaxCanonicalViewBytes,
		)
	}

	decoder := canonicalViewDecoder{raw: raw}
	domain, err := decoder.readString("domain")
	if err != nil {
		return View{}, err
	}
	if domain != ViewCanonicalizationVersion {
		return View{}, fmt.Errorf(
			"LayoutRoutingSecurityView canonical domain = %q, want %q",
			domain,
			ViewCanonicalizationVersion,
		)
	}

	view := View{CanonicalizationVersion: domain}
	if view.OrgID, err = decoder.readString("OrgID"); err != nil {
		return View{}, err
	}
	if view.KeyGrammarVersion, err = decoder.readString("KeyGrammarVersion"); err != nil {
		return View{}, err
	}
	if view.RoleTopologyGrammarVersion, err = decoder.readString("RoleTopologyGrammarVersion"); err != nil {
		return View{}, err
	}

	profileCount, err := decoder.readCount("Profile count", MaxProfileIdentities)
	if err != nil {
		return View{}, err
	}
	view.Profiles = make([]ProfileIdentity, int(profileCount))
	for index := range view.Profiles {
		identity, err := decoder.readProfile(index)
		if err != nil {
			return View{}, err
		}
		view.Profiles[index] = identity
	}

	roleCount, err := decoder.readCount("Role count", MaxRoleIdentities)
	if err != nil {
		return View{}, err
	}
	view.Roles = make([]RoleIdentity, int(roleCount))
	for index := range view.Roles {
		identity, err := decoder.readRole(index)
		if err != nil {
			return View{}, err
		}
		view.Roles[index] = identity
	}
	if decoder.offset != len(raw) {
		return View{}, fmt.Errorf(
			"LayoutRoutingSecurityView canonical bytes contain %d trailing bytes",
			len(raw)-decoder.offset,
		)
	}
	if err := ValidateView(view); err != nil {
		return View{}, err
	}
	canonical, err := CanonicalViewBytes(view)
	if err != nil {
		return View{}, err
	}
	if !bytes.Equal(canonical, raw) {
		return View{}, fmt.Errorf("LayoutRoutingSecurityView canonical bytes are not exact")
	}
	return view, nil
}

type canonicalViewDecoder struct {
	raw    []byte
	offset int
}

func (decoder *canonicalViewDecoder) readProfile(index int) (ProfileIdentity, error) {
	var identity ProfileIdentity
	var err error
	label := fmt.Sprintf("Profiles[%d]", index)
	if identity.Key.NamespaceID, err = decoder.readString(label + ".ProfileKey.NamespaceID"); err != nil {
		return ProfileIdentity{}, err
	}
	if identity.Key.LocalKey, err = decoder.readString(label + ".ProfileKey.LocalKey"); err != nil {
		return ProfileIdentity{}, err
	}
	if identity.ResolvedProfileID, err = decoder.readString(label + ".ResolvedProfileID"); err != nil {
		return ProfileIdentity{}, err
	}
	identity.State, err = decoder.readState(label + ".State")
	return identity, err
}

func (decoder *canonicalViewDecoder) readRole(index int) (RoleIdentity, error) {
	var identity RoleIdentity
	var err error
	label := fmt.Sprintf("Roles[%d]", index)
	if identity.Key.NamespaceID, err = decoder.readString(label + ".RoleKey.NamespaceID"); err != nil {
		return RoleIdentity{}, err
	}
	if identity.Key.LocalKey, err = decoder.readString(label + ".RoleKey.LocalKey"); err != nil {
		return RoleIdentity{}, err
	}
	if identity.ResolvedRoleID, err = decoder.readString(label + ".ResolvedRoleID"); err != nil {
		return RoleIdentity{}, err
	}
	if identity.State, err = decoder.readState(label + ".State"); err != nil {
		return RoleIdentity{}, err
	}
	present, err := decoder.readByte(label + ".ParentRoleKey.present")
	if err != nil {
		return RoleIdentity{}, err
	}
	switch present {
	case 0:
		return identity, nil
	case 1:
		parent := RoleKey{}
		if parent.NamespaceID, err = decoder.readString(label + ".ParentRoleKey.NamespaceID"); err != nil {
			return RoleIdentity{}, err
		}
		if parent.LocalKey, err = decoder.readString(label + ".ParentRoleKey.LocalKey"); err != nil {
			return RoleIdentity{}, err
		}
		identity.ParentRoleKey = &parent
		return identity, nil
	default:
		return RoleIdentity{}, fmt.Errorf(
			"LayoutRoutingSecurityView %s = 0x%02x, want 0x00 or 0x01",
			label+".ParentRoleKey.present",
			present,
		)
	}
}

func (decoder *canonicalViewDecoder) readState(label string) (IdentityState, error) {
	value, err := decoder.readByte(label)
	if err != nil {
		return "", err
	}
	switch value {
	case 0x01:
		return IdentityStateActive, nil
	case 0x02:
		return IdentityStateTombstone, nil
	default:
		return "", fmt.Errorf(
			"LayoutRoutingSecurityView %s = 0x%02x, want ACTIVE or TOMBSTONE",
			label,
			value,
		)
	}
}

func (decoder *canonicalViewDecoder) readCount(label string, maximum int) (uint64, error) {
	value, err := decoder.readUint(label)
	if err != nil {
		return 0, err
	}
	if value > uint64(maximum) {
		return 0, fmt.Errorf(
			"LayoutRoutingSecurityView %s = %d, exceeds %d",
			label,
			value,
			maximum,
		)
	}
	return value, nil
}

func (decoder *canonicalViewDecoder) readString(label string) (string, error) {
	length, err := decoder.readUint(label + " length")
	if err != nil {
		return "", err
	}
	remaining := len(decoder.raw) - decoder.offset
	if length > uint64(remaining) {
		return "", fmt.Errorf(
			"LayoutRoutingSecurityView %s length %d exceeds remaining bytes %d",
			label,
			length,
			remaining,
		)
	}
	end := decoder.offset + int(length)
	value := string(decoder.raw[decoder.offset:end])
	decoder.offset = end
	return value, nil
}

func (decoder *canonicalViewDecoder) readUint(label string) (uint64, error) {
	if decoder.offset == len(decoder.raw) {
		return 0, fmt.Errorf("LayoutRoutingSecurityView %s is truncated", label)
	}
	value, count := binary.Uvarint(decoder.raw[decoder.offset:])
	switch {
	case count == 0:
		return 0, fmt.Errorf("LayoutRoutingSecurityView %s is truncated", label)
	case count < 0:
		return 0, fmt.Errorf("LayoutRoutingSecurityView %s overflows unsigned LEB128", label)
	case count != int(canonicalUintSize(value)):
		return 0, fmt.Errorf("LayoutRoutingSecurityView %s uses nonminimal unsigned LEB128", label)
	}
	decoder.offset += count
	return value, nil
}

func (decoder *canonicalViewDecoder) readByte(label string) (byte, error) {
	if decoder.offset == len(decoder.raw) {
		return 0, fmt.Errorf("LayoutRoutingSecurityView %s is truncated", label)
	}
	value := decoder.raw[decoder.offset]
	decoder.offset++
	return value, nil
}
