package commands

import (
	"context"
	"flag"
	"fmt"
	"os"
	"regexp"

	"github.com/google/subcommands"
	"github.com/inconshreveable/log15"
	c "github.com/mozqnet/go-exploitdb/config"
	"github.com/mozqnet/go-exploitdb/db"
	"github.com/mozqnet/go-exploitdb/models"
	"github.com/mozqnet/go-exploitdb/util"
)

var (
	cveIDRegexp       = regexp.MustCompile(`^CVE-\d{1,}-\d{1,}$`)
	exploitDBIDRegexp = regexp.MustCompile(`^\d{1,}$`)
)

// SearchExploitCmd :
type SearchExploitCmd struct {
	Debug       bool
	DebugSQL    bool
	Quiet       bool
	LogDir      string
	LogJSON     bool
	DBPath      string
	DBType      string
	HTTPProxy   string
	SearchType  string
	SearchParam string
}

// Name :
func (*SearchExploitCmd) Name() string { return "search" }

// Synopsis :
func (*SearchExploitCmd) Synopsis() string { return "search Exploit from go-exploitdb." }

// Usage :
func (*SearchExploitCmd) Usage() string {
	return `search:
	search
		[-dbtype=sqlite3|mysql|postgres|redis]
		[-dbpath=$PWD/exploitdb.sqlite3 or connection string]
		[-http-proxy=http://192.168.0.1:8080]
		[-debug]
		[-debug-sql]
		[-quiet]
		[-log-dir=/path/to/log]
		[-log-json]
		[-stype]
		[-sparam]

  Command:
	$ go-exploitdb search -stype [All/CVE/ID] -sparam [CVE-xxxx-xxxx/xxxx]
	
  ex.
  [*] If you want to search All Exploits
	$ go-exploitdb search -stype All

  [*] If you want to search Exploit by CVE
	$ go-exploitdb search -stype CVE -sparam CVE-xxxx-xxxx

  [*] If you want to search Exploit by ExploitDB-ID
	$ go-exploitdb search -stype ID -sparam xxxx

`
}

// SetFlags :
func (p *SearchExploitCmd) SetFlags(f *flag.FlagSet) {
	f.BoolVar(&p.Debug, "debug", false, "debug mode")
	f.BoolVar(&p.DebugSQL, "debug-sql", false, "SQL debug mode")
	f.BoolVar(&p.Quiet, "quiet", false, "quiet mode (no output)")
	defaultLogDir := util.GetDefaultLogDir()
	f.StringVar(&p.LogDir, "log-dir", defaultLogDir, "/path/to/log")
	f.BoolVar(&p.LogJSON, "log-json", false, "output log as JSON")

	pwd := os.Getenv("PWD")
	f.StringVar(&p.DBPath, "dbpath", pwd+"/go-exploitdb.sqlite3",
		"/path/to/sqlite3 or SQL connection string")

	f.StringVar(&p.DBType, "dbtype", "sqlite3",
		"Database type to store data in (sqlite3, mysql, postgres or redis supported)")

	f.StringVar(
		&p.HTTPProxy,
		"http-proxy",
		"",
		"http://proxy-url:port (default: empty)",
	)

	f.StringVar(
		&p.SearchType,
		"stype",
		"CVE",
		"All Exploits by CVE: CVE  |  by ID: ID (default: CVE)",
	)

	f.StringVar(
		&p.SearchParam,
		"sparam",
		"",
		"All Exploits: None  |  by CVE: [CVE-xxxx]  |  by ID: [xxxx]  (default: None)",
	)
}

// Execute :
func (p *SearchExploitCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
	c.CommonConf.Quiet = p.Quiet
	c.CommonConf.DebugSQL = p.DebugSQL
	c.CommonConf.DBPath = p.DBPath
	c.CommonConf.DBType = p.DBType
	c.CommonConf.HTTPProxy = p.HTTPProxy
	c.SearchConf.SearchType = p.SearchType
	c.SearchConf.SearchParam = p.SearchParam

	util.SetLogger(p.LogDir, c.CommonConf.Quiet, c.CommonConf.Debug, p.LogJSON)
	if !c.CommonConf.Validate() {
		return subcommands.ExitUsageError
	}

	driver, locked, err := db.NewDB(c.CommonConf.DBType, c.CommonConf.DBPath, c.CommonConf.DebugSQL)
	if err != nil {
		if locked {
			log15.Error("Failed to Open DB. Close DB connection", "err", err)
		}
		return subcommands.ExitFailure
	}
	if err := driver.MigrateDB(); err != nil {
		log15.Error("Failed to migrate DB", "err", err)
		return subcommands.ExitFailure
	}

	stype := c.SearchConf.SearchType
	param := c.SearchConf.SearchParam

	var results = []*models.Exploit{}
	switch stype {
	case "CVE":
		if !cveIDRegexp.Match([]byte(param)) {
			log15.Error("Specify the search type [CVE] parameters in the format [CVE-xxxx-xxxx].")
			return subcommands.ExitFailure
		}
		results = driver.GetExploitByCveID(param)
	case "ID":
		if !exploitDBIDRegexp.MatchString(param) {
			log15.Error("Specify the search type [ID] parameters in the format [xxxx].")
			return subcommands.ExitFailure
		}
		results = driver.GetExploitByID(param)
	default:
		log15.Error("Specify the search type [ CVE / ID].")
		return subcommands.ExitFailure
	}
	fmt.Println("")
	fmt.Println("Results: ")
	fmt.Println("---------------------------------------")
	if len(results) == 0 {
		fmt.Println("No Record Found")
	}
	for _, r := range results {
		fmt.Println("\n[*]CVE-ExploitID Reference:")
		fmt.Printf("  CVE: %s\n", r.CveID)
		fmt.Printf("  Exploit Type: %s\n", r.ExploitType)
		fmt.Printf("  Exploit Unique ID: %s\n", r.ExploitUniqueID)
		fmt.Printf("  URL: %s\n", r.URL)
		fmt.Printf("  Description: %s\n", r.Description)
		fmt.Printf("\n[*]Exploit Detail Info: ")
		if r.OffensiveSecurity != nil {
			fmt.Printf("\n  [*]OffensiveSecurity: ")
			os := r.OffensiveSecurity
			if os.Document != nil {
				fmt.Println("\n  - Ducument:")
				fmt.Printf("    Path: %s\n", os.Document.DocumentURL)
				fmt.Printf("    File Type: %s\n", os.Document.Type)
			}
			if os.ShellCode != nil {
				fmt.Println("\n  - Exploit Code or Proof of Concept:")
				fmt.Printf("    %s\n", os.ShellCode.ShellCodeURL)
			}
			if os.Paper != nil {
				fmt.Println("\n  - Paper:")
				fmt.Printf("    %s\n", os.Paper.PaperURL)
			}
		}
		fmt.Println("---------------------------------------")
	}

	return subcommands.ExitSuccess
}
