package srnd

import (
	"bufio"
	"crypto/rand"
	"crypto/rsa"
	"crypto/tls"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"io"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/textproto"
	"os"
	"path/filepath"
	"strings"
	"time"
)

var TlsNotSupported = errors.New("TLS not supported")
var TlsFailedToLoadCA = errors.New("could not load CA files")

// handle STARTTLS on connection
func HandleStartTLS(conn net.Conn, config *tls.Config) (econn *textproto.Conn, state tls.ConnectionState, err error) {
	if config == nil {
		_, err = io.WriteString(conn, "580 can not intitiate TLS negotiation\r\n")
		if err == nil {
			err = TlsNotSupported
		}
	} else {
		_, err = io.WriteString(conn, "382 Continue with TLS negotiation\r\n")
		if err == nil {
			// begin tls crap here
			tconn := tls.Server(conn, config)
			err = tconn.Handshake()
			state = tconn.ConnectionState()
			if err == nil {
				econn = textproto.NewConn(tconn)
				return
			} else {
				log.Println("tls handshake error: ", err.Error())
				certs := state.PeerCertificates
				if len(certs) == 0 {
					log.Println("starttls failed, no peer certs provided")
				} else {
					for _, cert := range certs {
						for _, dns := range cert.DNSNames {
							log.Println("starttls peer cert from", dns, "not valid")
						}
					}
				}
				tconn.Close()
				conn.Close()
			}
		}
	}
	return
}

func SendStartTLS(conn net.Conn, config *tls.Config) (econn *textproto.Conn, state tls.ConnectionState, err error) {
	_, err = io.WriteString(conn, "STARTTLS\r\n")
	if err == nil {
		r := bufio.NewReader(conn)
		var line string
		line, err = r.ReadString(10)
		if strings.HasPrefix(line, "382 ") {
			// we gud
			tconn := tls.Client(conn, config)
			// tls okay
			log.Println("TLS Handshake done", config.ServerName)
			state = tconn.ConnectionState()
			econn = textproto.NewConn(tconn)
			return
		} else {
			// it won't do tls
			err = TlsNotSupported
		}
		r = nil
	}
	return
}

// create base tls certificate
func newTLSCert() x509.Certificate {
	return x509.Certificate{
		Subject: pkix.Name{
			Organization: []string{"overchan"},
		},
		NotBefore:             time.Now(),
		NotAfter:              time.Date(9005, 1, 1, 1, 1, 1, 1, time.UTC),
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth},
		BasicConstraintsValid: true,
		IsCA: true,
	}
}

// generate tls config, private key and certificate
func GenTLS(cfg *CryptoConfig) (tcfg *tls.Config, err error) {
	EnsureDir(cfg.cert_dir)
	// check for private key
	if !CheckFile(cfg.privkey_file) {
		// no private key, let's generate it
		log.Println("generating 4096 RSA private key...")
		k := newTLSCert()
		var priv *rsa.PrivateKey
		priv, err = rsa.GenerateKey(rand.Reader, 4096)
		if err == nil {
			serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 256)
			k.SerialNumber, err = rand.Int(rand.Reader, serialNumberLimit)
			k.DNSNames = append(k.DNSNames, cfg.hostname)
			k.Subject.CommonName = cfg.hostname
			if err == nil {
				var derBytes []byte
				derBytes, err = x509.CreateCertificate(rand.Reader, &k, &k, &priv.PublicKey, priv)
				var f io.WriteCloser
				f, err = os.Create(cfg.cert_file)
				if err == nil {
					err = pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
					f.Close()
					if err == nil {
						f, err = os.Create(cfg.privkey_file)
						if err == nil {
							err = pem.Encode(f, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
							f.Close()
						}
					}
				}
			}
		}
	}
	if err == nil {

		caPool := x509.NewCertPool()
		var m []string
		log.Println("checking", cfg.cert_dir, "for certificates")
		m, err = filepath.Glob(filepath.Join(cfg.cert_dir, "*.crt"))
		log.Println("loading", len(m), "trusted certificates")
		var data []byte
		for _, f := range m {
			var d []byte
			d, err = ioutil.ReadFile(f)
			if err == nil {
				data = append(data, d...)
			} else {
				return
			}
		}
		ok := caPool.AppendCertsFromPEM(data)
		if !ok {
			err = TlsFailedToLoadCA
			return
		}
		// we should have the key generated and stored by now
		var cert tls.Certificate
		cert, err = tls.LoadX509KeyPair(cfg.cert_file, cfg.privkey_file)
		if err == nil {
			tcfg = &tls.Config{
				CipherSuites: []uint16{tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384},
				RootCAs:      caPool,
				ClientCAs:    caPool,
				Certificates: []tls.Certificate{cert},
				ClientAuth:   tls.RequireAndVerifyClientCert,
			}
		}
	}
	return
}
