package ftp

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	log "github.com/cihub/seelog"
	"net"
	"strings"
	"time"
)

// FTP is a Session for file Transfer Protocol
type FTPS struct {
	conn      net.Conn
	addr      string
	tlsconfig *tls.Config

	reader *bufio.Reader
	writer *bufio.Writer

	isFtps bool
}

func (fs *FTPS) close() {
	fs.conn.Close()
}

func (fs *FTPS) quit() (err error) {
	if _, err := fs.cmd(statusCloseConnect, "QUIT"); err != nil {
		return err
	}
	fs.conn.Close()
	fs.conn = nil
	return nil
}

func (fs *FTPS) cmd(expects string, cmd string) (line string, err error) {
	if err = fs.send(cmd); err != nil {
		log.Error("ftps.go cmd send error: ", err)
		return "", err
	}

	if line, err = fs.receive(); err != nil {
		log.Error("ftps.go cmd receive error: ", err)
		return line, err
	}

	if !strings.HasPrefix(line, expects) {
		err = errors.New(line)
		return line, err
	}

	return line, err
}

func (fs *FTPS) readAndDiscard() (int, error) {
	var i int
	bufferSize := fs.reader.Buffered()

	for i = 0; i < bufferSize; i++ {
		if _, err := fs.reader.ReadByte(); err != nil {
			return i, err
		}
	}

	return i, nil
}

func (fs *FTPS) receive() (string, error) {
	line, err := fs.reader.ReadString('\n')
	//log.Debug("< %s", line)

	if err != nil {
		return line, err
	}

	fs.readAndDiscard()
	//fmt.Println(line)
	return line, err
}

func (fs *FTPS) send(cmd string) (err error) {

	if len(cmd) == 0 {
		err = errors.New("command length 0")
	}

	cmd = fmt.Sprintf(cmd)
	cmd += "\r\n"

	if _, err := fs.writer.WriteString(cmd); err != nil {
		return err
	}

	if err := fs.writer.Flush(); err != nil {
		return err
	}
	return nil
}

func (fs *FTPS) authTls(config *tls.Config) error {
	if _, err := fs.cmd(statusTlsUseOK, "AUTH TLS"); err != nil {
		return err
	}

	fs.tlsconfig = config

	fs.conn = tls.Client(fs.conn, config)
	fs.writer = bufio.NewWriter(fs.conn)
	fs.reader = bufio.NewReader(fs.conn)

	_, err := fs.cmd(statusOK, "PBSZ 0")

	if err != nil {
		return err
	}

	_, err = fs.cmd(statusOK, "PROT P")

	if err != nil {
		return err
	}

	return nil
}
func (fs *FTPS) NewFTPSConnect(addr string) (*FTPS, error) {
	var err error
	var conn net.Conn

	if conn, err = net.Dial("tcp", addr); err != nil {
		log.Error("FTPS Socket Fail: ", err.Error())
		return nil, err
	}

	err = conn.SetDeadline(time.Now().Add(3 * time.Second))
	if err != nil {
		//log.Error("FTPS Socket Fail: ", err.Error())
		return nil, err
	}

	writer := bufio.NewWriter(conn)
	reader := bufio.NewReader(conn)

	var line string

	obj := &FTPS{
		conn:   conn,
		addr:   addr,
		reader: reader,
		writer: writer,
	}

	line, err = obj.receive()

	if !strings.HasPrefix(line, "220") {
		err = errors.New(line)
		return nil, err
	}

	//log.Debug(line)

	return obj, err
}

func StartCheckFTPS(ip string, port string) (bool, error) {

	var err error
	var fs *FTPS

	addr := ip + ":" + port
	//log.Debug("address : " + addr)

	if fs, err = fs.NewFTPSConnect(addr); err != nil {
		return false, err
	}

	defer fs.close()

	config := &tls.Config{
		InsecureSkipVerify: true,
		ClientAuth:         tls.RequestClientCert,
	}

	if err = fs.authTls(config); err != nil {
		return false, err
	}

	return true, err
}
