package analytics

import (
	"context"
	"encoding/json"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

	"github.com/spf13/cobra"

	"github.com/ignite/cli/v29/ignite/config"
	"github.com/ignite/cli/v29/ignite/internal/sentry"
	"github.com/ignite/cli/v29/ignite/pkg/cliui"
	"github.com/ignite/cli/v29/ignite/pkg/errors"
	"github.com/ignite/cli/v29/ignite/pkg/randstr"
	"github.com/ignite/cli/v29/ignite/version"
)

const (
	telemetryEndpoint  = "https://api.ignite.com/v1/telemetry"
	envDoNotTrack      = "DO_NOT_TRACK"
	envCI              = "CI"
	envGitHubActions   = "GITHUB_ACTIONS"
	igniteAnonIdentity = "anon_identity.json"
)

var matomoClient MatomoClient

// anonIdentity represents an analytics identity file.
type anonIdentity struct {
	// Name represents the username.
	Name string `json:"name" yaml:"name"`
	// DoNotTrack represents the user track choice.
	DoNotTrack bool `json:"doNotTrack" yaml:"doNotTrack"`
}

func init() {
	matomoClient = NewMatomoClient(
		telemetryEndpoint,
		WithIDSite(4),
		WithSource("https://cli.ignite.com"),
	)
}

// SendMetric send command metrics to analytics.
func SendMetric(wg *sync.WaitGroup, cmd *cobra.Command) {
	if cmd.Name() == "version" {
		return
	}

	dntInfo, err := checkDNT()
	if err != nil || dntInfo.DoNotTrack {
		return
	}

	versionInfo, err := version.GetInfo(context.Background())
	if err != nil {
		return
	}

	var (
		path         = cmd.CommandPath()
		scaffoldType = ""
	)
	if strings.Contains(path, "ignite scaffold") {
		splitCMD := strings.Split(path, " ")
		if len(splitCMD) > 2 {
			scaffoldType = splitCMD[2]
		}
	}

	met := Metric{
		Name:            cmd.Name(),
		Cmd:             path,
		ScaffoldType:    scaffoldType,
		OS:              versionInfo.OS,
		Arch:            versionInfo.Arch,
		Version:         versionInfo.CLIVersion,
		CLIVersion:      versionInfo.CLIVersion,
		GoVersion:       versionInfo.GoVersion,
		SDKVersion:      versionInfo.SDKVersion,
		BuildDate:       versionInfo.BuildDate,
		SourceHash:      versionInfo.SourceHash,
		ConfigVersion:   versionInfo.ConfigVersion,
		Uname:           versionInfo.Uname,
		CWD:             versionInfo.CWD,
		BuildFromSource: versionInfo.BuildFromSource,
		IsCI:            getIsCI(),
	}

	wg.Add(1)
	go func() {
		defer wg.Done()
		_ = matomoClient.SendMetric(dntInfo.Name, met)
	}()
}

// EnableSentry enable errors reporting to Sentry.
func EnableSentry(ctx context.Context, wg *sync.WaitGroup) {
	dntInfo, err := checkDNT()
	if err != nil || dntInfo.DoNotTrack {
		return
	}

	closeSentry, err := sentry.InitSentry(ctx)
	wg.Add(1)
	go func() {
		defer wg.Done()
		if err == nil {
			defer closeSentry()
		}
	}()
}

// checkDNT check if the user allow to track data or if the DO_NOT_TRACK
// env var is set https://consoledonottrack.com/
func checkDNT() (anonIdentity, error) {
	if dnt := os.Getenv(envDoNotTrack); dnt != "" {
		if dnt, err := strconv.ParseBool(dnt); err != nil || dnt {
			return anonIdentity{DoNotTrack: true}, nil
		}
	}

	globalPath, err := config.DirPath()
	if err != nil {
		return anonIdentity{}, err
	}
	if err := os.Mkdir(globalPath, 0o700); err != nil && !os.IsExist(err) {
		return anonIdentity{}, err
	}

	identityPath := filepath.Join(globalPath, igniteAnonIdentity)
	data, err := os.ReadFile(identityPath)
	if err != nil && !os.IsNotExist(err) {
		return anonIdentity{}, err
	}

	var i anonIdentity
	if err := json.Unmarshal(data, &i); err == nil {
		return i, nil
	}

	i.Name = randstr.Runes(16)
	i.DoNotTrack = false

	message := "Ignite uses anonymized metrics to enhance the application, " +
		"focusing on features such as command usage. We do not collect " +
		"identifiable personal information. Your privacy is important to us. " +
		"For more details, please visit our Privacy Policy at https://ignite.com/privacy " +
		"and our Terms of Use at https://ignite.com/terms-of-use. " +
		"Do you consent to the collection of these usage metrics for analytics purposes?"

	session := cliui.New()
	err = session.AskConfirm(message)
	if err != nil && !errors.Is(err, cliui.ErrAbort) {
		return anonIdentity{}, err
	}

	if errors.Is(err, cliui.ErrAbort) {
		i.DoNotTrack = true
	}

	data, err = json.Marshal(&i)
	if err != nil {
		return i, err
	}

	return i, os.WriteFile(identityPath, data, 0o600)
}

func getIsCI() bool {
	ci, err := strconv.ParseBool(os.Getenv(envCI))
	if err != nil {
		return false
	}

	if ci {
		return true
	}

	ci, err = strconv.ParseBool(os.Getenv(envGitHubActions))
	if err != nil {
		return false
	}

	return ci
}
