package main

import (
	"encoding/hex"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	_ "net/http/pprof"
	"os"
	"path/filepath"
	"runtime"
	"strconv"

	"google.golang.org/grpc"

	"github.com/lightningnetwork/lnd/chainntnfs/btcdnotify"
	"github.com/lightningnetwork/lnd/channeldb"
	"github.com/lightningnetwork/lnd/lnrpc"
	"github.com/lightningnetwork/lnd/lnwallet"
	"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
	"github.com/roasbeef/btcrpcclient"
)

var (
	cfg             *config
	shutdownChannel = make(chan struct{})
)

// lndMain is the true entry point for lnd. This function is required since
// defers created in the top-level scope of a main method aren't executed if
// os.Exit() is called.
func lndMain() error {
	// Load the configuration, and parse any command line options. This
	// function will also set up logging properly.
	loadedConfig, err := loadConfig()
	if err != nil {
		return err
	}
	cfg = loadedConfig
	defer backendLog.Flush()

	// Show version at startup.
	ltndLog.Infof("Version %s", version())

	if loadedConfig.SPVMode == true {
		shell(loadedConfig.SPVHostAdr, activeNetParams.Params)
		return err
	}

	// Enable http profiling server if requested.
	if cfg.Profile != "" {
		go func() {
			listenAddr := net.JoinHostPort("", cfg.Profile)
			profileRedirect := http.RedirectHandler("/debug/pprof",
				http.StatusSeeOther)
			http.Handle("/", profileRedirect)
			fmt.Println(http.ListenAndServe(listenAddr, nil))
		}()
	}

	// Open the channeldb, which is dedicated to storing channel, and
	// network related meta-data.
	chanDB, err := channeldb.Open(loadedConfig.DataDir, activeNetParams.Params)
	if err != nil {
		fmt.Println("unable to open channeldb: ", err)
		return err
	}
	defer chanDB.Close()

	// Next load btcd's TLS cert for the RPC connection. If a raw cert was
	// specified in the config, then we'll se that directly. Otherwise, we
	// attempt to read the cert from the path specified in the config.
	var rpcCert []byte
	if loadedConfig.RawRPCCert != "" {
		rpcCert, err = hex.DecodeString(loadedConfig.RawRPCCert)
		if err != nil {
			return err
		}
	} else {
		certFile, err := os.Open(loadedConfig.RPCCert)
		if err != nil {
			return err
		}
		rpcCert, err = ioutil.ReadAll(certFile)
		if err != nil {
			return err
		}
		if err := certFile.Close(); err != nil {
			return err
		}
	}

	rpcIP, err := net.LookupHost(loadedConfig.RPCHost)
	if err != nil {
		fmt.Printf("unable to resolve rpc host: %v", err)
		return err
	}

	btcdHost := fmt.Sprintf("%v:%v", loadedConfig.RPCHost, activeNetParams.rpcPort)
	btcdUser := loadedConfig.RPCUser
	btcdPass := loadedConfig.RPCPass

	// TODO(roasbeef): parse config here and select chosen notifier instead
	rpcConfig := &btcrpcclient.ConnConfig{
		Host:                 btcdHost,
		Endpoint:             "ws",
		User:                 btcdUser,
		Pass:                 btcdPass,
		Certificates:         rpcCert,
		DisableTLS:           false,
		DisableConnectOnNew:  true,
		DisableAutoReconnect: false,
	}
	notifier, err := btcdnotify.New(rpcConfig)
	if err != nil {
		return err
	}

	// TODO(roasbeef): paarse config here select chosen WalletController
	walletConfig := &btcwallet.Config{
		PrivatePass: []byte("hello"),
		DataDir:     filepath.Join(loadedConfig.DataDir, "lnwallet"),
		RpcHost:     fmt.Sprintf("%v:%v", rpcIP[0], activeNetParams.rpcPort),
		RpcUser:     loadedConfig.RPCUser,
		RpcPass:     loadedConfig.RPCPass,
		CACert:      rpcCert,
		NetParams:   activeNetParams.Params,
	}
	wc, err := btcwallet.New(walletConfig)
	if err != nil {
		fmt.Printf("unable to create wallet controller: %v\n", err)
		return err
	}
	signer := wc
	bio := wc

	// Create, and start the lnwallet, which handles the core payment
	// channel logic, and exposes control via proxy state machines.
	wallet, err := lnwallet.NewLightningWallet(chanDB, notifier,
		wc, signer, bio, activeNetParams.Params)
	if err != nil {
		fmt.Printf("unable to create wallet: %v\n", err)
		return err
	}
	if err := wallet.Startup(); err != nil {
		fmt.Printf("unable to start wallet: %v\n", err)
		return err
	}
	ltndLog.Info("LightningWallet opened")

	// Set up the core server which will listen for incoming peer
	// connections.
	defaultListenAddrs := []string{
		net.JoinHostPort("", strconv.Itoa(loadedConfig.PeerPort)),
	}
	server, err := newServer(defaultListenAddrs, notifier, bio, wallet, chanDB)
	if err != nil {
		srvrLog.Errorf("unable to create server: %v\n", err)
		return err
	}
	if err := server.Start(); err != nil {
		srvrLog.Errorf("unable to create to start: %v\n", err)
		return err
	}

	addInterruptHandler(func() {
		ltndLog.Infof("Gracefully shutting down the server...")
		server.Stop()
		server.WaitForShutdown()
	})

	// Initialize, and register our implementation of the gRPC server.
	var opts []grpc.ServerOption
	grpcServer := grpc.NewServer(opts...)
	lnrpc.RegisterLightningServer(grpcServer, server.rpcServer)

	// Finally, start the grpc server listening for HTTP/2 connections.
	lis, err := net.Listen("tcp", fmt.Sprintf("localhost:%d", loadedConfig.RPCPort))
	if err != nil {
		fmt.Printf("failed to listen: %v", err)
		return err
	}
	go func() {
		rpcsLog.Infof("RPC server listening on %s", lis.Addr())
		grpcServer.Serve(lis)
	}()

	// Wait for shutdown signal from either a graceful server stop or from
	// the interrupt handler.
	<-shutdownChannel
	ltndLog.Info("Shutdown complete")
	return nil
}

func main() {
	// Use all processor cores.
	// TODO(roasbeef): remove this if required version # is > 1.6?
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Call the "real" main in a nested manner so the defers will properly
	// be executed in the case of a graceful shutdown.
	if err := lndMain(); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}
