package smb

import (
	"errors"
	"fmt"
	"net"
	"sync/atomic"

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

//TODO tcp发过去的消息会乱序, 需要用msgid chan来处理单独的协议. 马上send马上recv的消息可能msgId对不上

type Options struct {
	Host        string
	Port        int
	Workstation string
	Domain      string
	User        string
	Password    string
}

func validateOptions(opt Options) error {
	if opt.Host == "" {
		return errors.New("Missing required option: Host")
	}
	if opt.Port < 1 || opt.Port > 65535 {
		return errors.New("Invalid or missing value: Port")
	}
	return nil
}

type SessionC struct {
	session

	//client level
	trees     map[string]uint32
	options   Options
	messageID uint64 //for client msgId
	Challenge ntlmssp.Challenge
}

func NewSessionClient(opt Options, debug bool) (s *SessionC, err error) {

	if err := validateOptions(opt); err != nil {
		return nil, err
	}

	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%d", opt.Host, opt.Port))
	if err != nil {
		return
	}

	s = &SessionC{
		session: session{
			IsSigningRequired: false,
			IsAuthenticated:   false,
			debug:             debug,
			conn:              conn,
			securityMode:      0,
			sessionID:         0,
			dialect:           0,
			// trees:             make(map[string]uint32),
		},
		options: opt,
	}

	s.Debug("Negotiating protocol", nil)
	err = s.NegotiateProtocolClient()
	if err != nil {
		return
	}
	return s, nil
}

func (s *SessionC) NegotiateProtocolClient() error {
	s.Debug("Sending NegotiateProtocol request", nil)

	// myMechType := gss.KRB5SSPMechTypeOid
	myMechType := gss.NtLmSSPMechTypeOid

	if true {
		requestNego := s.NewNegotiateRequest()
		responseNego := &NegotiateResponse{
			SecurityBlob: &gss.NegTokenInit{},
		}
		if err := s.RPC(requestNego, responseNego); err != nil {
			return err
		}
		if err := requestNego.ClientAction(s, responseNego); err != nil {
			return err
		}
	}

	if true {
		s.Debug("Sending SessionSetup1 request", nil)
		setupRequest1 := s.NewSessionSetup1Request(myMechType)
		setupResponse1 := &SessionSetup1Response{
			SecurityBlob: &gss.NegTokenResp{},
		}
		if err := s.RPC(setupRequest1, setupResponse1); err != nil {
			return err
		}
		setupRequest1.ClientAction(s, setupResponse1)
	}

	if true {
		s.Debug("Sending SessionSetup2 request", nil)
		setupRequest2, err := s.NewSessionSetup2Request()
		if err != nil {
			return err
		}
		setupResponse2 := &SessionSetup2Response{
			SecurityBlob: &gss.NegTokenResp{},
		}
		if err = s.RPC(setupRequest2, setupResponse2); err != nil {
			return err
		}
		setupRequest2.ClientAction(s, setupResponse2)
	}

	return nil
}

func (s *SessionC) Close() {
	s.Debug("Closing session", nil)
	for k, _ := range s.trees {
		s.TreeDisconnect(k)
	}
	s.Debug("Closing TCP connection", nil)
	s.conn.Close()
	s.Debug("Session close completed", nil)
}

func (s *SessionC) newHeader(cmd Command) Header {
	msgId := atomic.AddUint64(&s.messageID, 1)
	return newHeader(cmd, msgId, s.sessionID)

	// return Header{
	// 	ProtocolID:   []byte(ProtocolSmb2),
	// 	HeaderLength: 64,
	// 	Command:      cmd,
	// 	MessageID:    msgId,
	// 	SessionID:    s.sessionID,
	// 	Signature:    make([]byte, 16),
	// 	CreditCharge: 1,
	// }
}

func newHeader(cmd Command, msgId, sessionId uint64) Header {
	return Header{
		ProtocolID:   []byte(ProtocolSmb2),
		HeaderLength: 64,
		Command:      cmd,
		MessageID:    msgId,
		SessionID:    sessionId, //  s.sessionID,
		Signature:    make([]byte, 16),
		CreditCharge: 1,
	}
}
