package gss

import (
	"encoding/asn1"
	"fmt"
	"log"

	"imuslab.com/smb/driver/mod/smb/encoder"
)

// https://www.rfc-editor.org/rfc/rfc4178.html#section-4.1

type MechTypeOid string

const SpnegoOid = "1.3.6.1.5.5.2"
const NtLmSSPMechTypeOid MechTypeOid = "1.3.6.1.4.1.311.2.2.10"

// const KRB5SSPMechTypeOid MechTypeOid = "1.3.6.1.5.2.5"

const GssStateAcceptCompleted = 0
const GssStateAcceptIncomplete = 1
const GssStateReject = 2
const GssStateRequestMic = 3

type NegTokenInitData struct {
	MechTypes    []asn1.ObjectIdentifier `asn1:"explicit,tag:0"`
	ReqFlags     asn1.BitString          `asn1:"explicit,optional,omitempty,tag:1"`
	MechToken    []byte                  `asn1:"explicit,optional,omitempty,tag:2"`
	MechTokenMIC []byte                  `asn1:"explicit,optional,omitempty,tag:3"`
}

type NegTokenInit struct {
	OID  asn1.ObjectIdentifier
	Data NegTokenInitData `asn1:"explicit"`
}

type NegState int

const (
	Accept_completed  NegState = 0
	Accept_incomplete NegState = 1
	Reject            NegState = 2
	Request_mic       NegState = 3
)

type NegTokenResp struct {
	NegResult     asn1.Enumerated       `asn1:"explicit,optional,omitempty,tag:0"` //NegState
	SupportedMech asn1.ObjectIdentifier `asn1:"explicit,optional,omitempty,tag:1"`
	ResponseToken []byte                `asn1:"explicit,optional,omitempty,tag:2"`
	MechListMIC   []byte                `asn1:"explicit,optional,omitempty,tag:3"`
}

// gsswrapped used to force ASN1 encoding to include explicit sequence tags
// Type does not fulfill the BinaryMarshallable interfce and is used only as a
// helper to marshal a NegTokenResp
type gsswrapped struct{ G interface{} }

func NewNegTokenInit(MechType MechTypeOid) (*NegTokenInit, error) {
	oid, err := ObjectIDStrToInt(SpnegoOid)
	if err != nil {
		return &NegTokenInit{}, err
	}
	mechType, err := ObjectIDStrToInt(MechType)
	if err != nil {
		return &NegTokenInit{}, err
	}
	return &NegTokenInit{
		OID: oid,
		Data: NegTokenInitData{
			MechTypes:    []asn1.ObjectIdentifier{mechType},
			ReqFlags:     asn1.BitString{},
			MechToken:    []byte{},
			MechTokenMIC: []byte{},
		},
	}, nil
}

func NewNegTokenResp() *NegTokenResp {
	return &NegTokenResp{}
}

var _ encoder.BinaryMarshallable = (*NegTokenInit)(nil)

func (n *NegTokenInit) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	buf, err := asn1.Marshal(*n)
	if err != nil {
		log.Panicln(err)
		return nil, err
	}

	// When marshalling struct, asn1 uses 30 (sequence) tag by default.
	// Override to set 60 (application) to remain consistent with GSS/SMB
	buf[0] = 0x60
	return buf, nil
}

func (n *NegTokenInit) UnmarshalBinary(buf []byte, meta *encoder.Metadata) (interface{}, error) {
	data := NegTokenInit{}
	if _, err := asn1.UnmarshalWithParams(buf, &data, "application"); err != nil {
		return nil, err
	}
	*n = data
	return nil, nil
}

var _ encoder.BinaryMarshallable = (*NegTokenInit)(nil)

func (r *NegTokenResp) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	// Oddities in Go's ASN1 package vs SMB encoding mean we have to wrap our
	// struct in another struct to ensure proper tags and lengths are added
	// to encoded data
	wrapped := &gsswrapped{*r}
	ans, _ := wrapped.MarshalBinary(meta)
	fmt.Println("MAR", meta, ans)

	//return wrapped.MarshalBinary(meta)
	ans, err := wrapped.MarshalBinary(meta)
	if len(ans) == 4 && ans[0] == 0xa1 && ans[1] == 0x02 && ans[2] == 0x30 && ans[3] == 00 {
		return []byte{0xa1, 0x07, 0x30, 0x05, 0xa0, 0x03, 0x0a, 0x01, 0x00}, nil
	}
	return ans, err
	//return []byte{0xa1, 0x07, 0x30, 0x05, 0xa0, 0x03, 0x0a, 0x01, 0x00}, nil
}

func (r *NegTokenResp) UnmarshalBinary(buf []byte, meta *encoder.Metadata) (interface{}, error) {
	data := NegTokenResp{}
	if _, err := asn1.UnmarshalWithParams(buf, &data, "explicit,tag:1"); err != nil {
		return nil, err
	}
	*r = data
	return nil, nil
}

func (g *gsswrapped) MarshalBinary(meta *encoder.Metadata) ([]byte, error) {
	buf, err := asn1.Marshal(*g)
	if err != nil {
		return nil, err
	}
	buf[0] = 0xa1
	return buf, nil
}
