package main

import (
	"context"
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
	"time"

	"github.com/tidbcloud/aws-shared-provider/tools/cloud-instances/internal/cli"
	"github.com/tidbcloud/aws-shared-provider/tools/cloud-instances/query"
)

// Command-line flags
var (
	// Provider selection
	providerFlag = flag.String("provider", "aws", "Cloud provider: aws, azure, gcp, aliyun, all")
	regionFlag   = flag.String("region", "", "Region to query (provider-specific)")

	// Download options
	downloadFlag      = flag.Bool("download", false, "Download data from cloud provider")
	downloadOnlyFlag  = flag.Bool("download-only", false, "Download only, no analysis")
	forceDownloadFlag = flag.Bool("force-download", false, "Force download even if cache exists")
	cacheDirFlag      = flag.String("cache-dir", ".", "Cache directory path")
	maxCacheAgeFlag   = flag.String("max-cache-age", "", "Maximum cache age (e.g., 24h); older cache will be refreshed")

	// Query filters
	typeRegexFlag       = flag.String("type", "", "Filter by instance type regex (e.g., '^r7g')")
	familyFilterFlag    = flag.String("family", "", "Filter by instance family (e.g., 'Memory optimized')")
	vcpuFilterFlag      = flag.String("vcpu", "", "Filter by vCPU count (e.g., '64')")
	memoryFilterFlag    = flag.String("memory", "", "Filter by memory size (e.g., '32 GiB')")
	localStorageFlag    = flag.Bool("local-storage", false, "Filter instances with local disk (instance storage)")
	instanceStorageFlag = flag.Bool("instance-storage", false, "Alias for --local-storage")
	noLocalStorageFlag  = flag.Bool("no-local-storage", false, "Filter instances without local disk (EBS only)")
	minPriceFlag        = flag.Float64("min-price", 0, "Minimum price filter")
	maxPriceFlag        = flag.Float64("max-price", 0, "Maximum price filter")

	// Output options
	outputFormatFlag = flag.String("format", "table", "Output format: table, json, yaml, all")
	outputStyleFlag  = flag.String("style", "pretty", "Output style: pretty, compact")
	outputDetails    = flag.Bool("details", false, "Include full details in output")
	sortByFlag       = flag.String("sort", "", "Sort by: price, vcpu, memory (default: no sorting)")
	currencyFlag     = flag.String("currency", "USD", "Currency code for price filters (default: USD)")

	// Auth overrides (flags override environment variables)
	aliyunAccessKeyFlag = flag.String("aliyun-access-key", "", "Alibaba Cloud access key (overrides ALICLOUD_ACCESS_KEY)")
	aliyunSecretKeyFlag = flag.String("aliyun-secret-key", "", "Alibaba Cloud secret key (overrides ALICLOUD_SECRET_KEY)")
	aliyunTokenFlag     = flag.String("aliyun-security-token", "", "Alibaba Cloud security token (overrides ALICLOUD_SECURITY_TOKEN)")
	aliyunTimeoutFlag   = flag.String("aliyun-timeout", "", "Alibaba Cloud request timeout (e.g., 60s, overrides ALICLOUD_TIMEOUT)")

	azureAccessTokenFlag    = flag.String("azure-access-token", "", "Azure access token (overrides AZURE_ACCESS_TOKEN)")
	azureTenantIDFlag       = flag.String("azure-tenant-id", "", "Azure tenant ID (overrides AZURE_TENANT_ID)")
	azureClientIDFlag       = flag.String("azure-client-id", "", "Azure client ID (overrides AZURE_CLIENT_ID)")
	azureClientSecretFlag   = flag.String("azure-client-secret", "", "Azure client secret (overrides AZURE_CLIENT_SECRET)")
	azureSubscriptionIDFlag = flag.String("azure-subscription-id", "", "Azure subscription ID (overrides AZURE_SUBSCRIPTION_ID)")

	gcpProjectIDFlag   = flag.String("gcp-project-id", "", "GCP project ID (overrides GCP_PROJECT_ID)")
	gcpAccessTokenFlag = flag.String("gcp-access-token", "", "GCP access token (overrides GCP_ACCESS_TOKEN)")
	gcpCredsFlag       = flag.String("gcp-credentials", "", "Path to GCP service account JSON (overrides GOOGLE_APPLICATION_CREDENTIALS)")
	gcpAPIKeyFlag      = flag.String("gcp-api-key", "", "GCP billing API key (overrides GCP_API_KEY)")

	// SSO helpers
	azureSSOFlag  = flag.Bool("azure-sso", false, "Use Azure CLI SSO to fetch access token and subscription ID")
	gcpSSOFlag    = flag.Bool("gcp-sso", false, "Use gcloud CLI SSO to fetch access token and project ID")
	aliyunSSOFlag = flag.Bool("aliyun-sso", false, "Use Alibaba Cloud CLI config to load credentials")

	// Utility flags
	listProvidersFlag = flag.Bool("list-providers", false, "List available providers")
	listRegionsFlag   = flag.Bool("list-regions", false, "List available regions for provider")
	fromCacheFlag     = flag.Bool("from-cache", false, "Use only cached data, no downloads")
	clearCacheFlag    = flag.Bool("clear-cache", false, "Clear cache for specified provider/region")
	cacheInfoFlag     = flag.Bool("cache-info", false, "Show cache information")
)

func main() {
	// Parse flags
	flag.Parse()

	applyAuthFlags()
	if err := applySSOFlags(); err != nil {
		log.Fatalf("SSO error: %v", err)
	}

	// Set log output
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	// Create query engine
	engine := query.NewQueryEngine(*cacheDirFlag)

	// Handle utility commands first
	if *listProvidersFlag {
		listProviders(engine)
		return
	}

	if *listRegionsFlag {
		listRegions(engine)
		return
	}

	if *cacheInfoFlag {
		showCacheInfo(engine)
		return
	}

	if *clearCacheFlag {
		clearCache(engine)
		return
	}

	// Validate provider
	if *providerFlag == "" {
		fmt.Println("Error: Provider is required")
		flag.Usage()
		os.Exit(1)
	}

	// Build filter options
	var maxCacheAge time.Duration
	if *maxCacheAgeFlag != "" {
		parsed, err := time.ParseDuration(*maxCacheAgeFlag)
		if err != nil {
			log.Fatalf("Invalid --max-cache-age: %v", err)
		}
		maxCacheAge = parsed
	}

	localStorageEnabled := *localStorageFlag || *instanceStorageFlag
	if localStorageEnabled && *noLocalStorageFlag {
		log.Fatalf("Conflicting storage filters: --local-storage/--instance-storage and --no-local-storage")
	}
	localStorageFilter := ""
	if localStorageEnabled {
		localStorageFilter = "with"
	} else if *noLocalStorageFlag {
		localStorageFilter = "without"
	}

	opts := query.FilterOptions{
		Provider:           *providerFlag,
		Regions:            getRegions(*regionFlag),
		TypeRegex:          *typeRegexFlag,
		FamilyFilter:       *familyFilterFlag,
		VCPUFilter:         *vcpuFilterFlag,
		MemoryFilter:       *memoryFilterFlag,
		LocalStorageFilter: localStorageFilter,
		MinPrice:           *minPriceFlag,
		MaxPrice:           *maxPriceFlag,
		Currency:           *currencyFlag,
		FromCache:          *fromCacheFlag,
		CacheDir:           *cacheDirFlag,
		MaxCacheAge:        maxCacheAge,
		ForceDownload:      *forceDownloadFlag,
		DownloadOnly:       *downloadOnlyFlag,
		OutputFormat:       *outputFormatFlag,
		OutputStyle:        *outputStyleFlag,
		SortBy:             *sortByFlag,
		ShowDetails:        *outputDetails,
	}

	if shouldDownload(engine, opts, *downloadFlag, *downloadOnlyFlag, *forceDownloadFlag, *fromCacheFlag) {
		if err := cli.ValidateAuth(engine, opts); err != nil {
			log.Fatalf("Authentication error: %v", err)
		}
	}

	// Handle download mode
	if *downloadFlag || *downloadOnlyFlag {
		if err := engine.Download(opts); err != nil {
			log.Fatalf("Download error: %v", err)
		}
		if *downloadOnlyFlag {
			return
		}
	}

	// Handle query mode (default if no download-only)
	if !*downloadOnlyFlag {
		// If no filters and no download flag, show usage
		if !*downloadFlag && !*fromCacheFlag &&
			*typeRegexFlag == "" &&
			*familyFilterFlag == "" &&
			*vcpuFilterFlag == "" &&
			*memoryFilterFlag == "" &&
			!localStorageEnabled &&
			!*noLocalStorageFlag &&
			*minPriceFlag == 0 &&
			*maxPriceFlag == 0 &&
			*regionFlag == "" {
			fmt.Println("No query criteria specified. Use --download to fetch data or add filters.")
			flag.Usage()
			os.Exit(1)
		}

		if err := engine.QueryAndOutput(opts); err != nil {
			log.Fatalf("Query error: %v", err)
		}
	}
}

// getRegions parses region flag into slice
func getRegions(regionFlag string) []string {
	if regionFlag == "" {
		return nil
	}
	return strings.Split(regionFlag, ",")
}

// listProviders lists all available providers
func listProviders(engine *query.QueryEngine) {
	providers := engine.ListProviders()
	fmt.Println("Available providers:")
	for _, p := range providers {
		fmt.Printf("  - %s\n", p)
	}
}

// listRegions lists regions for a provider
func listRegions(engine *query.QueryEngine) {
	regions, err := engine.ListRegions(*providerFlag)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	fmt.Printf("Available regions for %s:\n", *providerFlag)
	for id, name := range regions {
		fmt.Printf("  %-20s %s\n", id, name)
	}
}

// showCacheInfo displays cache information
func showCacheInfo(engine *query.QueryEngine) {
	cacheFiles, err := engine.GetCacheInfo(*providerFlag)
	if err != nil {
		log.Fatalf("Error: %v", err)
	}

	if len(cacheFiles) == 0 {
		fmt.Printf("No cached data found for %s\n", *providerFlag)
		return
	}

	fmt.Printf("Cached data for %s:\n", *providerFlag)
	for _, file := range cacheFiles {
		age, err := getCacheAgeForFile(engine, *providerFlag, file)
		if err != nil {
			fmt.Printf("  %s (unknown age)\n", file)
		} else {
			fmt.Printf("  %s (age: %v)\n", file, age)
		}
	}
}

// getCacheAgeForFile extracts region from cache filename and gets age
func getCacheAgeForFile(engine *query.QueryEngine, provider, cacheFile string) (string, error) {
	// Extract region from filename (format: provider_region.json)
	// Remove .json extension and provider prefix
	parts := strings.Split(cacheFile, "_")
	if len(parts) < 2 {
		return "", fmt.Errorf("invalid cache filename format")
	}

	// Join all parts after provider name (in case region has underscores)
	regionPart := strings.TrimSuffix(strings.Join(parts[1:], "_"), ".json")

	age, err := engine.GetCacheAge(provider, regionPart)
	if err != nil {
		return "", err
	}

	return age.String(), nil
}

// clearCache clears cache for provider/region
func clearCache(engine *query.QueryEngine) {
	regions := getRegions(*regionFlag)
	if err := engine.ClearCache(*providerFlag, regions); err != nil {
		log.Fatalf("Error clearing cache: %v", err)
	}
	fmt.Println("Cache cleared successfully")
}

func init() {
	// Custom usage message
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [options]\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "A unified tool to query instance metadata across AWS, Azure, GCP, and Alibaba Cloud\n\n")
		fmt.Fprintf(os.Stderr, "Provider Selection:\n")
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "\nExamples:\n")
		fmt.Fprintf(os.Stderr, "  # Download AWS data for us-east-1\n")
		fmt.Fprintf(os.Stderr, "  %s --provider aws --region us-east-1 --download\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "  # Query AWS instances with type filter\n")
		fmt.Fprintf(os.Stderr, "  %s --provider aws --type \"^r7g\" --format table\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "  # Query all providers with memory filter\n")
		fmt.Fprintf(os.Stderr, "  %s --provider all --memory \"32 GiB\" --format json\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "  # Query AWS instances sorted by price\n")
		fmt.Fprintf(os.Stderr, "  %s --provider aws --region us-east-1 --format table --sort price\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "  # List available regions\n")
		fmt.Fprintf(os.Stderr, "  %s --provider aws --list-regions\n\n", os.Args[0])
		fmt.Fprintf(os.Stderr, "  # Use cached data only\n")
		fmt.Fprintf(os.Stderr, "  %s --provider azure --from-cache --family \"General purpose\"\n\n", os.Args[0])
	}
}

func applyAuthFlags() {
	if *aliyunAccessKeyFlag != "" {
		_ = os.Setenv("ALICLOUD_ACCESS_KEY", *aliyunAccessKeyFlag)
	}
	if *aliyunSecretKeyFlag != "" {
		_ = os.Setenv("ALICLOUD_SECRET_KEY", *aliyunSecretKeyFlag)
	}
	if *aliyunTokenFlag != "" {
		_ = os.Setenv("ALICLOUD_SECURITY_TOKEN", *aliyunTokenFlag)
	}
	if *aliyunTimeoutFlag != "" {
		_ = os.Setenv("ALICLOUD_TIMEOUT", *aliyunTimeoutFlag)
	}

	if *azureAccessTokenFlag != "" {
		_ = os.Setenv("AZURE_ACCESS_TOKEN", *azureAccessTokenFlag)
	}
	if *azureTenantIDFlag != "" {
		_ = os.Setenv("AZURE_TENANT_ID", *azureTenantIDFlag)
	}
	if *azureClientIDFlag != "" {
		_ = os.Setenv("AZURE_CLIENT_ID", *azureClientIDFlag)
	}
	if *azureClientSecretFlag != "" {
		_ = os.Setenv("AZURE_CLIENT_SECRET", *azureClientSecretFlag)
	}
	if *azureSubscriptionIDFlag != "" {
		_ = os.Setenv("AZURE_SUBSCRIPTION_ID", *azureSubscriptionIDFlag)
	}

	if *gcpProjectIDFlag != "" {
		_ = os.Setenv("GCP_PROJECT_ID", *gcpProjectIDFlag)
	}
	if *gcpAccessTokenFlag != "" {
		_ = os.Setenv("GCP_ACCESS_TOKEN", *gcpAccessTokenFlag)
	}
	if *gcpCredsFlag != "" {
		_ = os.Setenv("GOOGLE_APPLICATION_CREDENTIALS", *gcpCredsFlag)
	}
	if *gcpAPIKeyFlag != "" {
		_ = os.Setenv("GCP_API_KEY", *gcpAPIKeyFlag)
	}
}

func applySSOFlags() error {
	if *azureSSOFlag {
		if err := applyAzureSSO(); err != nil {
			return err
		}
	}
	if *gcpSSOFlag {
		if err := applyGCPSso(); err != nil {
			return err
		}
	}
	if *aliyunSSOFlag {
		if err := applyAliyunSSO(); err != nil {
			return err
		}
	}
	return nil
}

func applyAzureSSO() error {
	if os.Getenv("AZURE_ACCESS_TOKEN") == "" {
		token, err := runCommand("az", "account", "get-access-token", "--resource", "https://management.azure.com/", "--query", "accessToken", "-o", "tsv")
		if err != nil {
			return fmt.Errorf("azure sso failed to get access token: %v", err)
		}
		_ = os.Setenv("AZURE_ACCESS_TOKEN", token)
	}
	if os.Getenv("AZURE_SUBSCRIPTION_ID") == "" {
		sub, err := runCommand("az", "account", "show", "--query", "id", "-o", "tsv")
		if err != nil {
			return fmt.Errorf("azure sso failed to get subscription id: %v", err)
		}
		_ = os.Setenv("AZURE_SUBSCRIPTION_ID", sub)
	}
	return nil
}

func applyGCPSso() error {
	if os.Getenv("GCP_ACCESS_TOKEN") == "" {
		token, err := runCommand("gcloud", "auth", "print-access-token")
		if err != nil {
			return fmt.Errorf("gcp sso failed to get access token: %v", err)
		}
		_ = os.Setenv("GCP_ACCESS_TOKEN", token)
	}
	if os.Getenv("GCP_PROJECT_ID") == "" {
		project, err := runCommand("gcloud", "config", "get-value", "project")
		if err != nil {
			return fmt.Errorf("gcp sso failed to get project id: %v", err)
		}
		_ = os.Setenv("GCP_PROJECT_ID", project)
	}
	return nil
}

func applyAliyunSSO() error {
	if os.Getenv("ALICLOUD_ACCESS_KEY") != "" && os.Getenv("ALICLOUD_SECRET_KEY") != "" {
		return nil
	}
	creds, err := cli.LoadAliyunCredentials()
	if err != nil {
		return err
	}
	if creds.AccessKey == "" || creds.SecretKey == "" {
		return fmt.Errorf("aliyun sso did not return access key/secret")
	}
	_ = os.Setenv("ALICLOUD_ACCESS_KEY", creds.AccessKey)
	_ = os.Setenv("ALICLOUD_SECRET_KEY", creds.SecretKey)
	if creds.Token != "" {
		_ = os.Setenv("ALICLOUD_SECURITY_TOKEN", creds.Token)
	}
	return nil
}

func runCommand(name string, args ...string) (string, error) {
	ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
	defer cancel()

	cmd := exec.CommandContext(ctx, name, args...)
	out, err := cmd.CombinedOutput()
	if err != nil {
		if errors.Is(ctx.Err(), context.DeadlineExceeded) {
			return "", fmt.Errorf("%s timed out", name)
		}
		return "", fmt.Errorf("%s: %s", err, strings.TrimSpace(string(out)))
	}
	return strings.TrimSpace(string(out)), nil
}

func shouldDownload(engine *query.QueryEngine, opts query.FilterOptions, downloadFlag, downloadOnlyFlag, forceDownloadFlag, fromCacheFlag bool) bool {
	if downloadFlag || downloadOnlyFlag || forceDownloadFlag {
		return true
	}
	if fromCacheFlag {
		return false
	}
	return needsDownload(engine, opts)
}

func needsDownload(engine *query.QueryEngine, opts query.FilterOptions) bool {
	providers := cli.ResolveProviders(engine, opts.Provider)
	for name, p := range providers {
		regions := opts.Regions
		if len(regions) == 0 {
			for regionID := range p.GetRegions() {
				regions = append(regions, regionID)
			}
		}

		for _, region := range regions {
			if !p.IsValidRegion(region) {
				continue
			}

			if rawManifestMissing(engine, name, region, opts.MaxCacheAge) {
				return true
			}

			if providerNeedsGlobal(name) {
				if rawManifestMissing(engine, name, "global", opts.MaxCacheAge) {
					return true
				}
			}
		}
	}
	return false
}

func rawManifestMissing(engine *query.QueryEngine, provider, scope string, maxAge time.Duration) bool {
	path := engine.CacheMgr.RawPath(provider, scope, "manifest")
	if _, err := os.Stat(path); err != nil {
		return true
	}
	if maxAge > 0 && !engine.CacheMgr.IsCacheValid(path, maxAge) {
		return true
	}
	return false
}

func providerNeedsGlobal(provider string) bool {
	switch provider {
	case "azure", "gcp", "aliyun":
		return true
	default:
		return false
	}
}
