//go:generate generate_permissions permissions.yaml permissions.go databricks
package databricks

import (
	"fmt"
	"os"

	"github.com/fatih/color"
	"github.com/jedib0t/go-pretty/v6/table"

	"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/analyzers"
	"github.com/trufflesecurity/trufflehog/v3/pkg/analyzer/config"
	"github.com/trufflesecurity/trufflehog/v3/pkg/context"
)

var _ analyzers.Analyzer = (*Analyzer)(nil)

type Analyzer struct {
	Cfg *config.Config
}

func (a Analyzer) Type() analyzers.AnalyzerType {
	return analyzers.AnalyzerTypeDataBricks
}

func (a Analyzer) Analyze(ctx context.Context, credInfo map[string]string) (*analyzers.AnalyzerResult, error) {
	token, exist := credInfo["token"]
	if !exist {
		return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationValidateCredentials, analyzers.ServiceConfig, "", fmt.Errorf("key not found in credential info"))
	}

	domain, exist := credInfo["domain"]
	if !exist {
		return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationValidateCredentials, analyzers.ServiceConfig, "", fmt.Errorf("domain not found in credential info"))
	}

	info, err := AnalyzePermissions(ctx, a.Cfg, domain, token)
	if err != nil {
		return nil, analyzers.NewAnalysisError(a.Type().String(), analyzers.OperationAnalyzePermissions, analyzers.ServiceAPI, "", err)
	}

	return secretInfoToAnalyzerResult(info), nil
}

func AnalyzeAndPrintPermissions(cfg *config.Config, domain, token string) {
	ctx := context.Background()

	info, err := AnalyzePermissions(ctx, cfg, domain, token)
	if err != nil {
		// just print the error in cli and continue as a partial success
		color.Red("[x] Error : %s", err.Error())
	}

	if info == nil {
		color.Red("[x] Error : %s", "No information found")
		return
	}

	color.Green("[!] Valid DataBricks Access Token\n\n")

	printUserInfo(info.UserInfo)
	printTokenInfo(info.Tokens)
	printPermissions(info.TokenPermissionLevels)

	if len(info.Resources) > 0 {
		printResources(info.Resources)
	}

	color.Yellow("\n[i] Expires: %s", "N/A (Refer to Token Information Table)")
}

func AnalyzePermissions(ctx context.Context, cfg *config.Config, domain, token string) (*SecretInfo, error) {
	client := analyzers.NewAnalyzeClient(cfg)

	var secretInfo = &SecretInfo{}

	if err := captureUserInfo(ctx, client, domain, token, secretInfo); err != nil {
		return nil, err
	}

	if err := captureTokensInfo(ctx, client, domain, token, secretInfo); err != nil {
		return secretInfo, err
	}

	if err := captureTokenPermissions(ctx, client, domain, token, secretInfo); err != nil {
		return secretInfo, err
	}

	// capture resources
	if err := captureDataBricksResources(ctx, client, domain, token, secretInfo); err != nil {
		return secretInfo, err
	}

	return secretInfo, nil
}

// secretInfoToAnalyzerResult translate secret info to Analyzer Result
func secretInfoToAnalyzerResult(info *SecretInfo) *analyzers.AnalyzerResult {
	if info == nil {
		return nil
	}

	result := analyzers.AnalyzerResult{
		AnalyzerType: analyzers.AnalyzerTypeDataBricks,
		Metadata:     map[string]any{},
		Bindings:     make([]analyzers.Binding, 0),
	}

	// extract information from resource to create bindings and append to result bindings
	for _, resource := range info.Resources {
		binding := analyzers.Binding{
			Resource: analyzers.Resource{
				Name:               resource.Name,
				FullyQualifiedName: fmt.Sprintf("databricks/%s/%s", resource.Type, resource.ID), // e.g: netlify/site/123
				Type:               resource.Type,
				Metadata:           map[string]any{}, // to avoid panic
			},
		}

		for key, value := range resource.Metadata {
			binding.Resource.Metadata[key] = value
		}

		// for each permission add a binding to resource
		for _, perm := range info.TokenPermissionLevels {
			binding.Permission = analyzers.Permission{
				Value: perm,
			}

			result.Bindings = append(result.Bindings, binding)
		}
	}

	return &result
}

// cli print functions
func printUserInfo(user User) {
	color.Yellow("[i] User Information:")
	t := table.NewWriter()
	t.SetOutputMirror(os.Stdout)
	t.AppendHeader(table.Row{"ID", "UserName", "Primary Email"})
	t.AppendRow(table.Row{color.GreenString(user.ID), color.GreenString(user.UserName), color.GreenString(user.PrimaryEmail)})

	t.Render()
}

func printTokenInfo(tokens []Token) {
	color.Yellow("[i] Tokens Information:")
	t := table.NewWriter()
	t.SetOutputMirror(os.Stdout)
	t.AppendHeader(table.Row{"Name", "Expiry Time", "Created By", "Last Used At"})
	for _, token := range tokens {
		t.AppendRow(table.Row{color.GreenString(token.Name),
			color.GreenString(token.ExpiryTime), color.GreenString(token.CreatedBy), color.GreenString(token.LastUsedDay)})
	}
	t.Render()
}

func printPermissions(permissions []string) {
	color.Yellow("[i] Token Permission Levels:")
	t := table.NewWriter()
	t.SetOutputMirror(os.Stdout)
	t.AppendHeader(table.Row{"Permission Level"})
	for _, permission := range permissions {
		t.AppendRow(table.Row{color.GreenString(permission)})
	}
	t.Render()
}

func printResources(resources []DataBricksResource) {
	color.Yellow("[i] Resources:")
	t := table.NewWriter()
	t.SetOutputMirror(os.Stdout)
	t.AppendHeader(table.Row{"Name", "Type"})
	for _, resource := range resources {
		t.AppendRow(table.Row{color.GreenString(resource.Name), color.GreenString(resource.Type)})
	}
	t.Render()
}
