// Package main is the entry point for the AutoSSO application.
// It initializes all components and starts the permission management system.
//
// Requirements: 6.2, 6.7
package main

import (
	"context"
	"flag"
	"fmt"
	"os"
	"os/signal"
	"sync"
	"syscall"
	"time"

	"AutoSSO/internal/config"
	"AutoSSO/internal/feishu"
	"AutoSSO/internal/permission"
	"AutoSSO/internal/pkg/httpclient"
	"AutoSSO/internal/pkg/logger"
	"AutoSSO/internal/pkg/utils"
	"AutoSSO/internal/provider"
	"AutoSSO/internal/provider/alibaba"
	"AutoSSO/internal/provider/aws"
	"AutoSSO/internal/provider/azure"
	"AutoSSO/internal/provider/gcp"
	"AutoSSO/internal/state"
)

const (
	// DefaultConfigPath is the default path to the configuration file
	DefaultConfigPath = "config/config.yaml"

	// DefaultShutdownTimeout is the maximum time to wait for graceful shutdown
	DefaultShutdownTimeout = 30 * time.Second
)

// Application holds all the components of the AutoSSO application.
// It manages the lifecycle of all services and handles graceful shutdown.
type Application struct {
	config       *config.Config
	logger       *logger.Logger
	httpClient   *httpclient.Client
	feishuClient *feishu.Client
	stateStore   state.Store
	registry     *provider.Registry
	manager      *permission.Manager
	monitor      *permission.Monitor
	lifecycle    *permission.Lifecycle

	// wg tracks all running goroutines for graceful shutdown
	wg sync.WaitGroup

	// shutdownTimeout is the maximum time to wait for graceful shutdown
	shutdownTimeout time.Duration
}

// NewApplication creates a new Application instance.
func NewApplication(cfg *config.Config, log *logger.Logger) *Application {
	return &Application{
		config:          cfg,
		logger:          log,
		shutdownTimeout: DefaultShutdownTimeout,
	}
}

// Initialize sets up all application components.
// It creates and configures all services needed for the application.
//
// Parameters:
//   - ctx: Context for initialization operations
//
// Returns an error if initialization fails.
func (app *Application) Initialize(ctx context.Context) error {
	app.logger.Info("Initializing application components")

	// Initialize HTTP client with configured timeout
	httpCfg := httpclient.DefaultConfig()
	httpCfg.Timeout = app.config.Runtime.HTTPTimeout
	app.httpClient = httpclient.New(&httpCfg)
	app.logger.Debug("HTTP client initialized")

	// Initialize Feishu client
	app.feishuClient = feishu.NewClient(&app.config.Feishu, app.httpClient).WithLogger(app.logger)
	app.logger.Debug("Feishu client initialized")

	// Initialize state store
	var err error
	if app.config.Database.Host != "" {
		app.stateStore, err = state.NewMySQLStore(&app.config.Database)
		if err != nil {
			return fmt.Errorf("failed to initialize MySQL state store: %w", err)
		}
		app.logger.Debug("MySQL state store initialized")
	} else {
		fileStoreCfg := &state.FileStoreConfig{
			DataDir:     "data",
			LockTimeout: 10 * time.Second,
		}
		app.stateStore, err = state.NewFileStore(fileStoreCfg)
		if err != nil {
			return fmt.Errorf("failed to initialize file state store: %w", err)
		}
		app.logger.Debug("File state store initialized")
	}

	// Initialize provider registry
	app.registry, err = provider.NewRegistry(app.config)
	if err != nil {
		return fmt.Errorf("failed to initialize provider registry: %w", err)
	}
	app.logger.Debug("Provider registry initialized")

	// Register cloud providers based on configuration
	if err := app.registerProviders(ctx); err != nil {
		return fmt.Errorf("failed to register providers: %w", err)
	}

	// Initialize notification service
	notificationService := feishu.NewNotificationService(
		app.feishuClient,
		&app.config.Feishu,
	)

	// Initialize permission manager
	app.manager = permission.NewManager(
		app.registry,
		app.feishuClient,
		app.stateStore,
		app.config,
		app.logger,
	)
	app.logger.Debug("Permission manager initialized")

	// Initialize permission monitor
	app.monitor = permission.NewMonitor(
		app.manager,
		app.feishuClient,
		app.stateStore,
		app.config,
		app.logger,
	)
	app.logger.Debug("Permission monitor initialized")

	// Initialize lifecycle manager
	app.lifecycle = permission.NewLifecycle(
		app.manager,
		notificationService,
		app.stateStore,
		app.config,
		app.logger,
	)
	app.logger.Debug("Lifecycle manager initialized")

	app.logger.Info("Application initialization completed")
	return nil
}

// registerProviders creates and registers cloud provider implementations
// based on the configuration. Only providers with configured accounts are registered.
//
// Parameters:
//   - ctx: Context for initialization operations
//
// Returns an error if any provider fails to initialize.
func (app *Application) registerProviders(ctx context.Context) error {
	registeredCount := 0

	// Register AWS provider if accounts are configured
	if len(app.config.Providers.AWS.Accounts) > 0 {
		app.logger.Debug("Initializing AWS provider",
			logger.Int("account_count", len(app.config.Providers.AWS.Accounts)),
		)
		awsProvider, err := aws.NewAWSProvider(&app.config.Providers.AWS)
		if err != nil {
			return fmt.Errorf("failed to create AWS provider: %w", err)
		}
		awsProvider.WithLogger(app.logger)

		if err := app.registry.RegisterProvider(provider.ProviderAWS, awsProvider); err != nil {
			return fmt.Errorf("failed to register AWS provider: %w", err)
		}
		app.logger.Info("AWS provider registered",
			logger.Int("accounts", len(app.config.Providers.AWS.Accounts)),
		)
		registeredCount++
	} else {
		app.logger.Debug("AWS provider skipped (no accounts configured)")
	}

	// Register GCP provider if projects are configured
	if len(app.config.Providers.GCP.Projects) > 0 {
		app.logger.Debug("Initializing GCP provider",
			logger.Int("project_count", len(app.config.Providers.GCP.Projects)),
		)
		gcpProvider, err := gcp.NewGCPProvider(&app.config.Providers.GCP)
		if err != nil {
			return fmt.Errorf("failed to create GCP provider: %w", err)
		}
		if err := app.registry.RegisterProvider(provider.ProviderGCP, gcpProvider); err != nil {
			return fmt.Errorf("failed to register GCP provider: %w", err)
		}
		app.logger.Info("GCP provider registered",
			logger.Int("projects", len(app.config.Providers.GCP.Projects)),
		)
		registeredCount++
	} else {
		app.logger.Debug("GCP provider skipped (no projects configured)")
	}

	// Register Azure provider if subscriptions are configured
	if len(app.config.Providers.Azure.Subscriptions) > 0 {
		app.logger.Debug("Initializing Azure provider",
			logger.Int("subscription_count", len(app.config.Providers.Azure.Subscriptions)),
		)
		azureProvider, err := azure.NewAzureProvider(&app.config.Providers.Azure)
		if err != nil {
			return fmt.Errorf("failed to create Azure provider: %w", err)
		}
		if err := app.registry.RegisterProvider(provider.ProviderAzure, azureProvider); err != nil {
			return fmt.Errorf("failed to register Azure provider: %w", err)
		}
		app.logger.Info("Azure provider registered",
			logger.Int("subscriptions", len(app.config.Providers.Azure.Subscriptions)),
		)
		registeredCount++
	} else {
		app.logger.Debug("Azure provider skipped (no subscriptions configured)")
	}

	// Register Alibaba provider if accounts are configured
	if len(app.config.Providers.Alibaba.Accounts) > 0 {
		app.logger.Debug("Initializing Alibaba provider",
			logger.Int("account_count", len(app.config.Providers.Alibaba.Accounts)),
		)
		alibabaProvider, err := alibaba.NewAlibabaProvider(&app.config.Providers.Alibaba)
		if err != nil {
			return fmt.Errorf("failed to create Alibaba provider: %w", err)
		}
		alibabaProvider.WithLogger(app.logger)

		if err := app.registry.RegisterProvider(provider.ProviderAlibaba, alibabaProvider); err != nil {
			return fmt.Errorf("failed to register Alibaba provider: %w", err)
		}
		app.logger.Info("Alibaba provider registered",
			logger.Int("accounts", len(app.config.Providers.Alibaba.Accounts)),
		)
		registeredCount++
	} else {
		app.logger.Debug("Alibaba provider skipped (no accounts configured)")
	}

	if registeredCount == 0 {
		app.logger.Warn("No cloud providers registered - no accounts configured for any provider")
		return fmt.Errorf("no cloud providers configured: at least one provider must have accounts configured")
	}

	app.logger.Info("Provider registration completed",
		logger.Int("registered_providers", registeredCount),
	)
	return nil
}

// Start begins all background services.
// It starts all monitoring goroutines and returns immediately.
// The goroutines will continue running until the context is cancelled.
//
// Parameters:
//   - ctx: Context for cancellation and graceful shutdown
//
// Requirements: 6.2, 6.7
func (app *Application) Start(ctx context.Context) {
	app.logger.Info("Starting application services")

	// Start approval monitor
	app.startGoroutine(ctx, "approval-monitor", func(ctx context.Context) error {
		return app.monitor.StartApprovalMonitor(ctx)
	})

	// Start removal scheduler
	app.startGoroutine(ctx, "removal-scheduler", func(ctx context.Context) error {
		return app.lifecycle.StartRemovalScheduler(ctx)
	})

	// Start daily report scheduler
	app.startGoroutine(ctx, "daily-report-scheduler", func(ctx context.Context) error {
		return app.lifecycle.StartDailyReportScheduler(ctx)
	})

	app.logger.Info("All application services started")
}

// startGoroutine starts a goroutine with panic recovery and WaitGroup tracking.
// It ensures that panics are caught and logged, and that the WaitGroup is
// properly decremented when the goroutine exits.
//
// Parameters:
//   - ctx: Context for cancellation
//   - name: Name of the goroutine for logging
//   - fn: Function to run in the goroutine
//
// Requirements: 6.6, 6.7
func (app *Application) startGoroutine(ctx context.Context, name string, fn func(context.Context) error) {
	app.wg.Add(1)
	go func() {
		defer app.wg.Done()
		defer utils.RecoverWithHandler(name, func(info *utils.PanicInfo) {
			app.logger.Error("Goroutine panicked",
				logger.String("goroutine", name),
				logger.String("panic", fmt.Sprintf("%v", info.Value)),
				logger.String("stack", info.Stack),
			)
		})

		app.logger.Debug("Starting goroutine",
			logger.String("goroutine", name),
		)

		if err := fn(ctx); err != nil {
			// Only log if not a context cancellation (expected during shutdown)
			if ctx.Err() == nil {
				app.logger.Error("Goroutine exited with error",
					logger.String("goroutine", name),
					logger.Err(err),
				)
			} else {
				app.logger.Debug("Goroutine stopped",
					logger.String("goroutine", name),
				)
			}
		}
	}()
}

// Shutdown performs graceful shutdown of all application components.
// It waits for all goroutines to complete within the shutdown timeout.
//
// Returns an error if shutdown times out.
//
// Requirements: 6.7
func (app *Application) Shutdown() error {
	app.logger.Info("Initiating graceful shutdown",
		logger.Duration("timeout", app.shutdownTimeout),
	)

	// Stop lifecycle manager schedulers
	if app.lifecycle != nil {
		app.lifecycle.Stop()
	}

	// Wait for all goroutines to complete with timeout
	done := make(chan struct{})
	go func() {
		defer utils.RecoverWithHandler("shutdown-waiter", func(info *utils.PanicInfo) {
			app.logger.Error("Shutdown waiter panicked",
				logger.String("panic", fmt.Sprintf("%v", info.Value)),
				logger.String("stack", info.Stack),
			)
		})
		app.wg.Wait()
		close(done)
	}()

	select {
	case <-done:
		app.logger.Info("All goroutines stopped gracefully")
	case <-time.After(app.shutdownTimeout):
		app.logger.Warn("Shutdown timeout exceeded, some goroutines may not have stopped")
		return fmt.Errorf("shutdown timeout exceeded after %v", app.shutdownTimeout)
	}

	// Close resources
	if err := app.closeResources(); err != nil {
		app.logger.Warn("Error closing resources",
			logger.Err(err),
		)
	}

	app.logger.Info("Graceful shutdown completed")
	return nil
}

// closeResources closes all application resources.
func (app *Application) closeResources() error {
	var errs []error

	// Close state store
	if app.stateStore != nil {
		if err := app.stateStore.Close(); err != nil {
			errs = append(errs, fmt.Errorf("failed to close state store: %w", err))
		}
		app.logger.Debug("State store closed")
	}

	// Close provider registry
	if app.registry != nil {
		if err := app.registry.Close(); err != nil {
			errs = append(errs, fmt.Errorf("failed to close provider registry: %w", err))
		}
		app.logger.Debug("Provider registry closed")
	}

	// Close HTTP client
	if app.httpClient != nil {
		app.httpClient.Close()
		app.logger.Debug("HTTP client closed")
	}

	if len(errs) > 0 {
		return fmt.Errorf("errors closing resources: %v", errs)
	}
	return nil
}

// setupSignalHandler sets up signal handling for graceful shutdown.
// It returns a context that will be cancelled when a shutdown signal is received.
//
// Parameters:
//   - parentCtx: Parent context to derive from
//
// Returns a context and cancel function for shutdown coordination.
//
// Requirements: 6.2, 6.7
func setupSignalHandler(parentCtx context.Context) (context.Context, context.CancelFunc) {
	ctx, cancel := context.WithCancel(parentCtx)

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)

	go func() {
		defer utils.RecoverWithHandler("signal-handler", func(info *utils.PanicInfo) {
			// Use fmt since logger might not be available
			fmt.Printf("Signal handler panicked: %v\n%s\n", info.Value, info.Stack)
		})
		select {
		case sig := <-sigCh:
			fmt.Printf("\nReceived signal: %v, initiating shutdown...\n", sig)
			cancel()
		case <-ctx.Done():
			// Context was cancelled by other means
		}
		signal.Stop(sigCh)
	}()

	return ctx, cancel
}

func main() {
	// Parse command line flags
	configPath := flag.String("config", DefaultConfigPath, "Path to configuration file")
	flag.Parse()

	// Initialize logger (basic setup before config is loaded)
	logCfg := logger.DefaultConfig()
	logCfg.Level = logger.InfoLevel
	log := logger.NewLogger(logCfg)
	log.Info("AutoSSO starting",
		logger.String("config_path", *configPath),
	)

	// Load configuration
	cfg, err := config.Load(*configPath)
	if err != nil {
		log.Error("Failed to load configuration",
			logger.String("config_path", *configPath),
			logger.Err(err),
		)
		os.Exit(1)
	}

	// Validate configuration
	if err := cfg.Validate(); err != nil {
		log.Error("Configuration validation failed",
			logger.Err(err),
		)
		os.Exit(1)
	}

	// Update logger with configured log level
	logCfg.Level = logger.ParseLevel(cfg.Runtime.LogLevel)
	log = logger.NewLogger(logCfg)
	log.Info("Configuration loaded successfully",
		logger.String("log_level", cfg.Runtime.LogLevel),
	)

	// Set up signal handling for graceful shutdown
	ctx, cancel := setupSignalHandler(context.Background())
	defer cancel()

	// Create and initialize application
	app := NewApplication(cfg, log)
	if err := app.Initialize(ctx); err != nil {
		log.Error("Failed to initialize application",
			logger.Err(err),
		)
		os.Exit(1)
	}

	// Start all services
	app.Start(ctx)

	log.Info("AutoSSO is running, press Ctrl+C to stop")

	// Wait for shutdown signal
	<-ctx.Done()

	// Perform graceful shutdown
	if err := app.Shutdown(); err != nil {
		log.Error("Shutdown completed with errors",
			logger.Err(err),
		)
		os.Exit(1)
	}

	log.Info("AutoSSO stopped")
}
