package azure

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/Azure/go-autorest/autorest/azure/auth"

	"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
	"github.com/trufflesecurity/trufflehog/v3/pkg/pb/detectorspb"
)

type Scanner struct{}

// Ensure the Scanner satisfies the interface at compile time.
var _ detectors.Detector = (*Scanner)(nil)

func mustFmtPat(id, pat string) *regexp.Regexp {
	combinedID := strings.ReplaceAll(id, "_", "") + "|" + id
	return regexp.MustCompile(fmt.Sprintf(pat, combinedID))
}

var (
	// TODO: Azure storage access keys and investigate other types of creds.

	// Azure App Oauth
	idPatFmt    = `(?i)(%s).{0,20}([a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12})`
	clientIDPat = mustFmtPat("client_id", idPatFmt)
	tenantIDPat = mustFmtPat("tenant_id", idPatFmt)

	// TODO: support old patterns
	secretPatFmt    = `(?i)(%s).{0,20}([a-z0-9_\.\-~]{34})`
	clientSecretPat = mustFmtPat("client_secret", secretPatFmt)
)

// Keywords are used for efficiently pre-filtering chunks.
// Use identifiers in the secret preferably, or the provider name.
func (s Scanner) Keywords() []string {
	return []string{"azure"}
}

// FromData will find and optionally verify Azure secrets in a given set of bytes.
func (s Scanner) FromData(ctx context.Context, verify bool, data []byte) (results []detectors.Result, err error) {
	dataStr := string(data)

	clientSecretMatches := clientSecretPat.FindAllStringSubmatch(dataStr, -1)
	for _, clientSecret := range clientSecretMatches {
		tenantIDMatches := tenantIDPat.FindAllStringSubmatch(dataStr, -1)
		for _, tenantID := range tenantIDMatches {
			clientIDMatches := clientIDPat.FindAllStringSubmatch(dataStr, -1)
			for _, clientID := range clientIDMatches {
				s := detectors.Result{
					DetectorType: detectorspb.DetectorType_Azure,
					Raw:          []byte(clientSecret[2]),
					RawV2:        []byte(clientID[2] + clientSecret[2] + tenantID[2]),
					Redacted:     clientID[2],
				}

				if verify {
					cred := auth.NewClientCredentialsConfig(clientID[2], clientSecret[2], tenantID[2])
					token, err := cred.ServicePrincipalToken()
					if err != nil {
						continue
					}
					err = token.Refresh()
					if err == nil {
						s.Verified = true
					}
				}

				if !s.Verified {
					if detectors.IsKnownFalsePositive(s.Redacted, detectors.DefaultFalsePositives, true) {
						continue
					}
					if detectors.IsKnownFalsePositive(string(s.Raw), detectors.DefaultFalsePositives, true) {
						continue
					}
				}

				results = append(results, s)
			}
		}
	}

	return results, nil
}

func (s Scanner) Type() detectorspb.DetectorType {
	return detectorspb.DetectorType_Azure
}
