package router

import (
	"bytes"
	"context"
	"fmt"
	"os"
	"path"
	"path/filepath"

	pb "github.com/deepfence/agent-plugins-grpc/srcgo"
	dfUtils "github.com/deepfence/df-utils"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"

	"github.com/deepfence/ThreatMapper/deepfence_bootstrapper/supervisor"
	ctl "github.com/deepfence/ThreatMapper/deepfence_utils/controls"
	"github.com/deepfence/ThreatMapper/deepfence_utils/log"
	"github.com/deepfence/ThreatMapper/deepfence_utils/threatintel"
	"github.com/deepfence/ThreatMapper/deepfence_utils/utils"
)

var (
	ebpfMalwareSocketPath = dfUtils.GetDfInstallDir() + "/tmp/yara-hunter.sock"
	MalwareCertPath       = dfUtils.GetDfInstallDir() + "/etc/filebeat/filebeat.crt"

	// track rule hash
	malwareRuleshash string
)

var (
	MalwareScanConcurrency int
	MalwareMgmtConsoleURL  string
	MalwareDeepfenceKey    string
	MalwareScanDir         string
)

func init() {
	MalwareMgmtConsoleURL = os.Getenv("MGMT_CONSOLE_URL")
	consolePort := os.Getenv("MGMT_CONSOLE_PORT")
	if consolePort != "" && consolePort != "443" {
		MalwareMgmtConsoleURL += ":" + consolePort
	}
	MalwareDeepfenceKey = os.Getenv("DEEPFENCE_KEY")
	if os.Getenv("DF_SERVERLESS") == "true" {
		MalwareCertPath = "/deepfence/etc/filebeat/filebeat.crt"
		MalwareScanDir = "/"
	} else {
		MalwareScanDir = HostMountDir
	}
}

func StartMalwareScan(req ctl.StartMalwareScanRequest) error {
	log.Info().Msgf("Start malware scan: %v\n", req)
	var greq pb.MalwareRequest
	switch req.NodeType {
	case ctl.Container:
		greq = pb.MalwareRequest{
			Input: &pb.MalwareRequest_Container{
				Container: &pb.MalwareContainer{Id: req.BinArgs["node_id"]},
			},
			ScanId: req.BinArgs["scan_id"],
		}
	case ctl.Image:
		greq = pb.MalwareRequest{
			Input: &pb.MalwareRequest_Image{
				Image: &pb.MalwareDockerImage{Id: req.BinArgs["node_id"], Name: req.BinArgs["image_name"]},
			},
			ScanId: req.BinArgs["scan_id"],
		}
	case ctl.Host:
		greq = pb.MalwareRequest{
			Input:  &pb.MalwareRequest_Path{Path: MalwareScanDir},
			ScanId: req.BinArgs["scan_id"],
		}
	}

	conn, err := grpc.Dial("unix://"+ebpfMalwareSocketPath,
		grpc.WithAuthority("dummy"),
		grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("error in creating malware scanner client: %s\n", err.Error())
		return err
	}
	defer conn.Close()
	client := pb.NewMalwareScannerClient(conn)
	if err != nil {
		return err
	}
	_, err = client.FindMalwareInfo(context.Background(), &greq)
	if err != nil {
		return err
	}

	fmt.Printf("Malware scan started for scan id %s\n", req.BinArgs["scan_id"])

	return nil
}

func GetMalwareScannerJobCount() int32 {
	conn, err := grpc.Dial("unix://"+ebpfMalwareSocketPath,
		grpc.WithAuthority("dummy"),
		grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("error in creating malware scanner client: %s\n", err.Error())
		return 0
	}
	defer conn.Close()
	client := pb.NewScannersClient(conn)
	jobReport, err := client.ReportJobsStatus(context.Background(), &pb.Empty{})
	if err != nil {
		return 0
	}
	return jobReport.RunningJobs
}

func StopMalwareScan(req ctl.StopMalwareScanRequest) error {
	fmt.Printf("Stop Malware Scan : %v\n", req)
	conn, err := grpc.Dial("unix://"+ebpfMalwareSocketPath,
		grpc.WithAuthority("dummy"),
		grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		fmt.Printf("StopMalwareScannerJob::error in creating malware scanner client: %s\n", err.Error())
		return err
	}
	defer conn.Close()
	client := pb.NewScannersClient(conn)
	var greq pb.StopScanRequest
	greq.ScanId = req.BinArgs["scan_id"]

	_, err = client.StopScan(context.Background(), &greq)
	return err
}

// download, update rules and restart scanner
func UpdateMalwareRules(req ctl.ThreatIntelInfo) error {
	if req.MalwareRulesHash == malwareRuleshash {
		log.Warn().Msgf("skip malware rules update, already new")
		return nil
	}

	newRules := "new_malware_rules.tar.gz"
	rulesPath := path.Join(dfUtils.GetDfInstallDir(), "/home/deepfence/bin/yara-hunter/rules")

	if err := downloadFile(newRules, req.MalwareRulesURL); err != nil {
		log.Error().Err(err).Msg("failed to download malware rules")
		return err
	}
	defer os.Remove(newRules)

	log.Info().Msgf("completed downloading from url %s", req.MalwareRulesURL)

	// stop malware scanner
	if err := supervisor.StopProcess("malware_scanner"); err != nil {
		log.Error().Err(err).Msg("error on stop malware scanner")
	}

	// remove old rules
	os.RemoveAll(rulesPath)

	data, err := os.ReadFile(newRules)
	if err != nil {
		log.Error().Err(err).Msg("failed to open new rules")
		return err
	}

	if err := utils.ExtractTarGz(bytes.NewReader(data), filepath.Dir(rulesPath)); err != nil {
		log.Error().Err(err).Msg("failed to extract rules")
		return err
	}

	for _, infile := range []string{
		filepath.Join(rulesPath, "df-malware.json"),
	} {
		err = threatintel.ExtractDFRules2NativeRules(infile, rulesPath)
		if err != nil {
			return err
		}
	}

	log.Info().Msg("malware rules updated starting malware scanner")

	// start scanner
	if err := supervisor.StartProcess("malware_scanner"); err != nil {
		log.Error().Err(err).Msg("error on start malware scanner")
	}

	// set to new hash
	malwareRuleshash = req.MalwareRulesHash

	return nil
}
