/*
 *
 * Copyright 2022 gRPC authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package observability

import (
	"context"
	"fmt"
	"os"
	"regexp"

	gcplogging "cloud.google.com/go/logging"
	"golang.org/x/oauth2/google"
	configpb "google.golang.org/grpc/observability/internal/config"
	"google.golang.org/protobuf/encoding/protojson"
)

const (
	envObservabilityConfig    = "GRPC_CONFIG_OBSERVABILITY"
	envProjectID              = "GOOGLE_CLOUD_PROJECT"
	logFilterPatternRegexpStr = `^([\w./]+)/((?:\w+)|[*])$`
)

var logFilterPatternRegexp = regexp.MustCompile(logFilterPatternRegexpStr)

// fetchDefaultProjectID fetches the default GCP project id from environment.
func fetchDefaultProjectID(ctx context.Context) string {
	// Step 1: Check ENV var
	if s := os.Getenv(envProjectID); s != "" {
		logger.Infof("Found project ID from env %v: %v", envProjectID, s)
		return s
	}
	// Step 2: Check default credential
	credentials, err := google.FindDefaultCredentials(ctx, gcplogging.WriteScope)
	if err != nil {
		logger.Infof("Failed to locate Google Default Credential: %v", err)
		return ""
	}
	if credentials.ProjectID == "" {
		logger.Infof("Failed to find project ID in default credential: %v", err)
		return ""
	}
	logger.Infof("Found project ID from Google Default Credential: %v", credentials.ProjectID)
	return credentials.ProjectID
}

func validateFilters(config *configpb.ObservabilityConfig) error {
	for _, filter := range config.GetLogFilters() {
		if filter.Pattern == "*" {
			continue
		}
		match := logFilterPatternRegexp.FindStringSubmatch(filter.Pattern)
		if match == nil {
			return fmt.Errorf("invalid log filter pattern: %v", filter.Pattern)
		}
	}
	return nil
}

func parseObservabilityConfig() (*configpb.ObservabilityConfig, error) {
	// Parse the config from ENV var
	if content := os.Getenv(envObservabilityConfig); content != "" {
		var config configpb.ObservabilityConfig
		if err := protojson.Unmarshal([]byte(content), &config); err != nil {
			return nil, fmt.Errorf("error parsing observability config from env %v: %v", envObservabilityConfig, err)
		}
		if err := validateFilters(&config); err != nil {
			return nil, fmt.Errorf("error parsing observability config: %v", err)
		}
		logger.Infof("Parsed ObservabilityConfig: %+v", &config)
		return &config, nil
	}
	// If the ENV var doesn't exist, do nothing
	return nil, nil
}

func ensureProjectIDInObservabilityConfig(ctx context.Context, config *configpb.ObservabilityConfig) error {
	if config.GetDestinationProjectId() == "" {
		// Try to fetch the GCP project id
		projectID := fetchDefaultProjectID(ctx)
		if projectID == "" {
			return fmt.Errorf("empty destination project ID")
		}
		config.DestinationProjectId = projectID
	}
	return nil
}
