package smb

import (
	cryptorand "crypto/rand"
	"encoding/asn1"
	"errors"
	"fmt"
	"time"

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

const (
	// kMaxTransactSize     = 0x800000
	// kMaxTransactSizeSmb1 = 4194304
	kMaxTransactSize     = 1048576
	kMaxTransactSizeSmb1 = 1048576
)

// var gSPNEGOResponse = []byte{0x60, 0x48, 0x06, 0x06,
// 	0x2b, 0x06, 0x01, 0x05, 0x05, 0x02, 0xa0, 0x3e, 0x30, 0x3c, 0xa0, 0x0e, 0x30, 0x0c, 0x06, 0x0a,
// 	0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x02, 0x0a,
// 	0xa3, 0x2a, 0x30, 0x28, 0xa0, 0x26, 0x1b, 0x24, 0x6e, 0x6f, 0x74, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x5f, 0x69, 0x6e, 0x5f, 0x52, 0x46, 0x43, 0x34, 0x31, 0x37, 0x38, 0x40, 0x70, 0x6c, 0x65, 0x61, 0x73, 0x65, 0x5f, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65}

const (
	SMB2_GLOBAL_CAP_DFS                = 0x00000001
	SMB2_GLOBAL_CAP_LEASING            = 0x00000002
	SMB2_GLOBAL_CAP_LARGE_MTU          = 0x00000004
	SMB2_GLOBAL_CAP_MULTI_CHANNEL      = 0x00000008
	SMB2_GLOBAL_CAP_PERSISTENT_HANDLES = 0x00000010
	SMB2_GLOBAL_CAP_DIRECTORY_LEASING  = 0x00000020
	SMB2_GLOBAL_CAP_ENCRYPTION         = 0x00000040
)

func timeToFiletime(tm time.Time) uint64 {
	nsec := tm.UnixNano()
	// convert into 100-nanosecond
	nsec /= 100
	// change starting time to January 1, 1601
	nsec += 116444736000000000
	return uint64(nsec)
}

type HeaderSmb1 struct {
	ProtocolID  []byte `smb:"fixed:4"`
	SmbCommand  uint8  //0x72: negotiate protocol
	NtStatus    uint32
	Flags       uint8 //case sensitivity
	Flags2      uint16
	PricessId   uint16
	Signature   []byte `smb:"fixed:8"`
	Reserved    uint16
	TreeId      uint16
	ProcessId   uint16
	UserId      uint16
	MultiplexId uint16
}
type NegotiateSmb1Request struct {
	HeaderSmb1
	WordCount uint8
	ByteCount uint16
	// Dialects  []uint16
}
type NegotiateRequest struct {
	Header
	StructureSize   uint16
	DialectCount    uint16 `smb:"count:Dialects"`
	SecurityMode    uint16
	Reserved        uint16
	Capabilities    uint32
	ClientGuid      []byte `smb:"fixed:16"`
	ClientStartTime uint64
	Dialects        []uint16
}

var myMech = func() asn1.ObjectIdentifier {
	myMechType := gss.NtLmSSPMechTypeOid
	// Check for NTLMSSP support
	mySSPOID, err := gss.ObjectIDStrToInt(myMechType)
	if err != nil {
		panic(-1)
	}
	return asn1.ObjectIdentifier(mySSPOID)
}

type NegotiateResponse struct {
	Header
	StructureSize        uint16
	SecurityMode         uint16
	DialectRevision      uint16
	Reserved             uint16
	ServerGuid           []byte `smb:"fixed:16"`
	Capabilities         uint32
	MaxTransactSize      uint32
	MaxReadSize          uint32
	MaxWriteSize         uint32
	SystemTime           uint64
	ServerStartTime      uint64
	SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"`
	SecurityBufferLength uint16 `smb:"len:SecurityBlob"`
	Reserved2            uint32
	SecurityBlob         *gss.NegTokenInit
}

func (data *NegotiateSmb1Request) ServerAction(ctx *DataCtx) (interface{}, error) {

	req := &NegotiateRequest{
		Dialects: []uint16{DialectSmb_2_0_2},
	}

	resp := NegotiateResponse{
		Header:       newHeader(CommandNegotiate, 0, 0),
		SecurityBlob: &gss.NegTokenInit{},
	}
	resp.Header.CreditCharge = 0
	resp.Header.Credits = 1
	resp.Header.Status = StatusOk
	resp.Header.Flags = SMB2_FLAGS_RESPONSE
	resp.Header.Reserved = 1

	if true {
		//check dialect
		foundMyVer := false
		for _, d := range req.Dialects {
			if d == DialectSmb_2_1 || d == DialectSmb_2_0_2 {
				foundMyVer = true
			}
		}
		if !foundMyVer {
			return ERR(req.Header, STATUS_NOT_SUPPORTED)
		}
	}

	// ctx.session.dialect = uint16(DialectSmb_2_0_2)
	// ctx.session.dialect = uint16(DialectSmb_2_1)

	spnegoOID, err := gss.ObjectIDStrToInt(gss.SpnegoOid)
	if err != nil {
		return ERR(req.Header, STATUS_INVALID_PARAMETER)
	}
	resp.SecurityBlob.OID = asn1.ObjectIdentifier(spnegoOID)
	resp.SecurityBlob.Data.MechTypes = []asn1.ObjectIdentifier{myMech()}
	resp.SecurityMode = SecurityModeSigningEnabled //| SecurityModeSigningRequired 这个需要验证header的signature

	var gServerGuid []byte = func() []byte {
		// type GUID [16]byte
		var g GUID

		mustReadRand := func(dst []byte) {
			_, err := cryptorand.Read(dst[:])
			if err != nil {
				panic(err)
			}
		}

		mustReadRand(g[:])
		return g[:]
	}()

	resp.StructureSize = 0x41
	// resp.SecurityMode = 0x03
	resp.DialectRevision = DialectSmb2_ALL
	resp.ServerGuid = gServerGuid
	resp.Capabilities = SMB2_GLOBAL_CAP_DFS | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_LARGE_MTU
	resp.MaxTransactSize = kMaxTransactSizeSmb1
	resp.MaxReadSize = kMaxTransactSizeSmb1
	resp.MaxWriteSize = kMaxTransactSizeSmb1
	resp.SystemTime = timeToFiletime(time.Now())
	resp.SecurityBufferOffset = 0x80
	// resp.SecurityBufferLength = uint16(len(gSPNEGOResponse))

	return &resp, nil

	// return req.serverAction(ctx, resp)
}

func (data *NegotiateRequest) ServerAction(ctx *DataCtx) (interface{}, error) {
	resp := NegotiateResponse{
		SecurityBlob: &gss.NegTokenInit{},
	}
	resp.Header = data.Header
	resp.Header.Credits = 1
	resp.Header.Status = StatusOk
	resp.StructureSize = 65
	resp.Header.Flags = SMB2_FLAGS_RESPONSE

	if true {
		//check dialect
		foundMyVer := false
		for _, d := range data.Dialects {
			if d == DialectSmb_2_1 {
				foundMyVer = true
			}
		}
		if !foundMyVer {
			return ERR(data.Header, STATUS_NOT_SUPPORTED)
		}
	}

	fmt.Printf("clientSupportDialect: %v", data.Dialects)
	fmt.Println()
	ctx.session.dialect = uint16(DialectSmb_2_1)
	return data.serverAction(ctx, resp)
}

func (data *NegotiateRequest) serverAction(ctx *DataCtx, resp NegotiateResponse) (interface{}, error) {

	spnegoOID, err := gss.ObjectIDStrToInt(gss.SpnegoOid)
	if err != nil {
		return ERR(data.Header, STATUS_INVALID_PARAMETER)
	}
	resp.SecurityBlob.OID = asn1.ObjectIdentifier(spnegoOID)
	resp.SecurityBlob.Data.MechTypes = []asn1.ObjectIdentifier{myMech()}
	resp.SecurityMode = SecurityModeSigningEnabled //| SecurityModeSigningRequired 这个需要验证header的signature
	resp.DialectRevision = ctx.session.dialect

	var gServerGuid []byte = func() []byte {
		// type GUID [16]byte
		var g GUID

		mustReadRand := func(dst []byte) {
			_, err := cryptorand.Read(dst[:])
			if err != nil {
				panic(err)
			}
		}

		mustReadRand(g[:])
		return g[:]
	}()

	resp.ServerGuid = gServerGuid
	resp.Capabilities = SMB2_GLOBAL_CAP_DFS | SMB2_GLOBAL_CAP_LEASING | SMB2_GLOBAL_CAP_LARGE_MTU //| SMB2_GLOBAL_CAP_DIRECTORY_LEASING // TODO: check 3.3.5.4, page 259

	resp.MaxTransactSize = kMaxTransactSize
	resp.MaxReadSize = kMaxTransactSize
	resp.MaxWriteSize = kMaxTransactSize
	resp.SystemTime = timeToFiletime(time.Now())
	resp.SecurityBufferOffset = 0x80
	// resp.SecurityBufferLength = uint16(len(gSPNEGOResponse))

	return &resp, nil
}

func (negReq *NegotiateRequest) ClientAction(s *SessionC, negRes *NegotiateResponse) error {
	myMechType := gss.NtLmSSPMechTypeOid

	if negRes.Header.Status != StatusOk {
		return errors.New(fmt.Sprintf("NT Status Error: %d\n", negRes.Header.Status))
	}
	// Check SPNEGO security blob
	spnegoOID, err := gss.ObjectIDStrToInt(gss.SpnegoOid)
	if err != nil {
		return err
	}
	oid := negRes.SecurityBlob.OID
	if !oid.Equal(asn1.ObjectIdentifier(spnegoOID)) {
		return errors.New(fmt.Sprintf(
			"Unknown security type OID [expecting %s]: %s\n",
			gss.SpnegoOid,
			negRes.SecurityBlob.OID))
	}

	// Check for NTLMSSP support
	mySSPOID, err := gss.ObjectIDStrToInt(myMechType)
	if err != nil {
		s.Debug("", err)
		return err
	}

	hasMySSP := false
	for _, mechType := range negRes.SecurityBlob.Data.MechTypes {
		if mechType.Equal(asn1.ObjectIdentifier(mySSPOID)) {
			hasMySSP = true
			break
		}
	}
	if !hasMySSP {
		return errors.New("Server does not support NTLMSSP")
	}

	s.securityMode = negRes.SecurityMode
	s.dialect = negRes.DialectRevision

	// Determine whether signing is required
	mode := uint16(s.securityMode)
	if mode&SecurityModeSigningEnabled > 0 {
		if mode&SecurityModeSigningRequired > 0 {
			s.IsSigningRequired = true
		} else {
			s.IsSigningRequired = false
		}
	} else {
		s.IsSigningRequired = false
	}
	return nil
}

func (s *SessionC) NewNegotiateRequest() *NegotiateRequest {
	header := s.newHeader(CommandNegotiate)
	dialects := []uint16{
		uint16(DialectSmb_2_1),
		uint16(DialectSmb_3_0),
		uint16(DialectSmb_3_0_2),
	}
	return &NegotiateRequest{
		Header:          header,
		StructureSize:   36,
		DialectCount:    uint16(len(dialects)),
		SecurityMode:    SecurityModeSigningEnabled,
		Reserved:        0,
		Capabilities:    0,
		ClientGuid:      make([]byte, 16),
		ClientStartTime: 0,
		Dialects:        dialects,
	}
}
