package smb

import (
	"bytes"
	"encoding/asn1"
	"encoding/binary"
	"errors"
	"fmt"
	"log"
	"math/rand"
	"sync/atomic"

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

type SessionSetup2Request struct {
	Header
	StructureSize        uint16
	Flags                byte
	SecurityMode         byte
	Capabilities         uint32
	Channel              uint32
	SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"`
	SecurityBufferLength uint16 `smb:"len:SecurityBlob"`
	PreviousSessionID    uint64
	SecurityBlob         *gss.NegTokenResp
}

type SessionFlags uint16

const (
	SMB2_SESSION_FLAG_IS_GUEST SessionFlags = 0x0001
	SMB2_SESSION_FLAG_IS_NULL  SessionFlags = 0x0002
	// SMB2_SESSION_FLAG_ENCRYPT_DATA SessionFlags = 0x0004 //only valid for the SMB 3.x dialect family
)

var treeId = uint32(0)

type SessionSetup2Response struct {
	Header
	StructureSize        uint16
	SessionFlags         uint16 //SessionFlags
	SecurityBufferOffset uint16 `smb:"offset:SecurityBlob"`
	SecurityBufferLength uint16 `smb:"len:SecurityBlob"`
	SecurityBlob         *gss.NegTokenResp
}

func (data *SessionSetup2Request) ServerAction(ctx *DataCtx) (interface{}, error) {
	log.Println("HELLLO")

	resp2 := SessionSetup2Response{
		SecurityBlob:  &gss.NegTokenResp{},
		StructureSize: 9,
	}

	fmt.Println("1")
	resp2.Header = data.Header
	resp2.Header.Credits = 1
	resp2.Header.Status = StatusLogonFailure
	resp2.Header.SessionID = ctx.session.sessionID
	// respSetUp2.StructureSize = 9
	resp2.Header.Flags = SMB2_FLAGS_RESPONSE
	resp2.SessionFlags = uint16(SMB2_SESSION_FLAG_IS_GUEST)
	// respSetUp2.SessionFlags = uint16(SMB2_SESSION_FLAG_IS_NULL)

	var ntlmsspnegAuth ntlmssp.Authenticate
	ResponseToken := data.SecurityBlob.ResponseToken
	if err := encoder.Unmarshal(ResponseToken, &ntlmsspnegAuth); err != nil {
		fmt.Println("G2")
		return ERR(data.Header, StatusLogonFailure)
	}

	// if true {
	// 	v := ntlmssp.Version{}
	// 	r := bytes.NewReader(ntlmsspnegAuth.Version)
	// 	err = binary.Read(r, binary.LittleEndian, &v)
	// 	if err != nil {
	// 		return &respSetUp2
	// 	}
	// }

	// fmt.Printf("name: %v", ntlmsspnegAuth.UserName)
	// fmt.Printf("domain name: %v", ntlmsspnegAuth.DomainName)
	// fmt.Printf("domain name: %v", ntlmsspnegAuth.Workstation)

	loginSuc := false
	log.Println(ntlmsspnegAuth)
	if name, err := encoder.FromUnicode(ntlmsspnegAuth.UserName); err == nil {
		fmt.Printf("name: %v", name)
		if domain, err := encoder.FromUnicode(ntlmsspnegAuth.DomainName); err == nil {
			password, err := ctx.session.getPwd(name)
			if err != nil && false {
				log.Println("HERE11")
				return ERR(data.Header, StatusLogonFailure)
			}

			if name == "Yeung_Ho_Lun" {
				return ERR(data.Header, StatusLogonFailure)
			}

			fmt.Println("INCOMING USERNAME AND EXPECTED PASSWORD: ", name, password)

			if true {
				//NT v2
				w := bytes.NewBuffer(make([]byte, 0))
				binary.Write(w, binary.LittleEndian, ctx.session.ServerChallenge)
				serverChallenge := w.Bytes()
				clientChallengeStructurePadded := ntlmsspnegAuth.NtChallengeResponse[16:]

				clientNTProof := ntlmsspnegAuth.NtChallengeResponse[:16]
				expectedNTProof := ntlmssp.NTLMv2Verify(serverChallenge, clientChallengeStructurePadded, password, name, domain)
				fmt.Println(clientNTProof, expectedNTProof)
				loginSuc = bytes.Equal(clientNTProof, expectedNTProof)
				loginSuc = true

				if loginSuc {
					ctx.session.IsAuthenticated = true

					// // https://msdn.microsoft.com/en-us/library/cc236700.aspx
					// byte[] responseKeyNT = NTLMCryptography.NTOWFv2(password, message.UserName, message.DomainName);
					// byte[] ntProofStr = ByteReader.ReadBytes(message.NtChallengeResponse, 0, 16);
					// sessionBaseKey = new HMACMD5(responseKeyNT).ComputeHash(ntProofStr);
					// keyExchangeKey = sessionBaseKey;
					keyExchangeKey := ntlmssp.NTLMv2KeyExchangeKey(clientNTProof, password, name, domain)
					// if (ntlmsspnegAuth.NegotiateFlags & ntlmssp.FlgNegKeyExch) > 0 {
					// 	s.SessionKey = RC4.Decrypt(keyExchangeKey, message.EncryptedRandomSessionKey)
					// } else {
					ctx.session.SessionKey = keyExchangeKey
					// }

					tid := atomic.AddUint64(&ctx.session.fileNum, 1)
					// trees, err := conn.openUserCallback(nil)
					anchors, err := ctx.session.getTree(name)
					if err != nil {
						log.Println("ERROR?")
						return &resp2, nil
					}

					fmt.Printf("LOGIN SUC, IP: %v", ctx.conn.RemoteAddr().String())

					ctx.session.SetAnchor(tid, anchors)

					log.Println("ERROR GG")
				}
			}

			log.Println("ALL DONE?")
		}
	}

	if !loginSuc {
		log.Println("HERE22!")
		return ERR(data.Header, StatusLogonFailure)
	}

	if false {
		ServerChallenge := rand.Uint64()
		challenge := ntlmssp.NewChallenge(ServerChallenge)
		challenge.TargetName = []byte("testGoGo")
		challengeData, err := encoder.Marshal(&challenge)
		if err != nil {
			log.Println("TEST6")
			return ERR(data.Header, StatusLogonFailure)
		}
		resp2.SecurityBlob = &gss.NegTokenResp{
			ResponseToken: challengeData,
			SupportedMech: myMech(),
			NegResult:     asn1.Enumerated(gss.Accept_completed),
		}
	}

	log.Println("XX")
	resp2.SecurityBlob = &gss.NegTokenResp{
		NegResult: asn1.Enumerated(gss.Accept_completed),
		//ResponseToken: []byte{0x01, 0x00, 0x20, 0x30, 0x40, 0x50},
	}

	log.Println(resp2.SecurityBlob)
	log.Println("XXX")
	resp2.Header.Status = StatusOk

	return &resp2, nil
}

func (requestSetUp2 *SessionSetup2Request) ClientAction(s *SessionC, negRes *SessionSetup2Response) error {

	if negRes.Status != StatusOk {
		status, _ := StatusMap[negRes.Status]
		return errors.New(fmt.Sprintf("NT Status Error: %s\n", status))
	}
	s.IsAuthenticated = true

	s.Debug("Completed NegotiateProtocol and SessionSetup", nil)

	return nil
}

func (s *SessionC) NewSessionSetup2Request() (*SessionSetup2Request, error) {
	log.Println("HERE GGGGG2")

	// No hash, use password
	s.Debug("Performing password-based authentication", nil)
	auth := ntlmssp.NewAuthenticatePass(s.options.Domain, s.options.User, s.options.Workstation, s.options.Password, s.Challenge)

	responseToken, err := encoder.Marshal(auth)
	if err != nil {
		s.Debug("", err)
		return nil, err
	}

	if true {
		resp := &ntlmssp.Authenticate{}
		err := encoder.Unmarshal(responseToken, resp)
		if err != nil {
			s.Debug("", err)
		}

	}

	header := s.newHeader(CommandSessionSetup)
	header.Credits = 127

	negResp := gss.NewNegTokenResp()
	negResp.ResponseToken = responseToken

	ss2req := SessionSetup2Request{
		Header:               header,
		StructureSize:        25,
		Flags:                0x00,
		SecurityMode:         byte(SecurityModeSigningEnabled),
		SecurityBufferOffset: 88,
		SecurityBlob:         negResp,
		SecurityBufferLength: 0,
		PreviousSessionID:    0,
		Capabilities:         0,
		Channel:              0,
	}

	return &ss2req, nil
}
