//go:build stats

package events

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"

	"github.com/projectdiscovery/nuclei/v3/pkg/utils/json"
)

var _ ScanEventWorker = &ScanStatsWorker{}

var defaultWorker = &ScanStatsWorker{}

// ScanStatsWorker is a worker for scanning stats
// This tracks basic stats in jsonlines format
// in given directory or a default directory with name stats_{timestamp} in the current directory
type ScanStatsWorker struct {
	config    *ScanConfig
	m         *sync.Mutex
	directory string
	file      *os.File
	enc       json.Encoder
}

// Init initializes the scan stats worker
func InitWithConfig(config *ScanConfig, statsDirectory string) {
	currentTime := time.Now().Format("20060102150405")
	dirName := fmt.Sprintf("nuclei-stats-%s", currentTime)
	err := os.Mkdir(dirName, 0755)
	if err != nil {
		panic(err)
	}
	// save the config to the directory
	bin, err := json.MarshalIndent(config, "", "  ")
	if err != nil {
		panic(err)
	}
	err = os.WriteFile(filepath.Join(dirName, ConfigFile), bin, 0755)
	if err != nil {
		panic(err)
	}
	defaultWorker = &ScanStatsWorker{config: config, m: &sync.Mutex{}, directory: dirName}
	err = defaultWorker.initEventsFile()
	if err != nil {
		panic(err)
	}
}

// initEventsFile initializes the events file for the worker
func (s *ScanStatsWorker) initEventsFile() error {
	f, err := os.Create(filepath.Join(s.directory, EventsFile))
	if err != nil {
		return err
	}
	s.file = f
	s.enc = json.NewEncoder(f)
	return nil
}

// AddScanEvent adds a scan event to the worker
func (s *ScanStatsWorker) AddScanEvent(event ScanEvent) {
	s.m.Lock()
	defer s.m.Unlock()

	err := s.enc.Encode(event)
	if err != nil {
		panic(err)
	}
}

// AddScanEvent adds a scan event to the worker
func AddScanEvent(event ScanEvent) {
	if defaultWorker == nil {
		return
	}
	defaultWorker.AddScanEvent(event)
}

// Close closes the file associated with the worker
func (s *ScanStatsWorker) Close() {
	s.m.Lock()
	defer s.m.Unlock()

	if s.file != nil {
		_ = s.file.Close()
		s.file = nil
	}
}

// Close closes the file associated with the worker
func Close() {
	if defaultWorker == nil {
		return
	}
	defaultWorker.Close()
}
