package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/paramah/ai_devs4/s01e03/internal/api"
	"github.com/paramah/ai_devs4/s01e03/internal/config"
	"github.com/paramah/ai_devs4/s01e03/internal/domain"
	"github.com/paramah/ai_devs4/s01e03/internal/infrastructure/llm"
	"github.com/paramah/ai_devs4/s01e03/internal/infrastructure/packages"
	"github.com/paramah/ai_devs4/s01e03/internal/infrastructure/session"
	"github.com/paramah/ai_devs4/s01e03/internal/infrastructure/verify"
	"github.com/paramah/ai_devs4/s01e03/internal/infrastructure/weather"
	"github.com/paramah/ai_devs4/s01e03/internal/usecase"
)

func main() {
	configPath := flag.String("config", "config.json", "Path to configuration file")
	flag.Parse()

	// Setup logging to file
	if err := setupLogging(); err != nil {
		log.Fatalf("Failed to setup logging: %v", err)
	}

	// Load configuration
	cfg, err := config.Load(*configPath)
	if err != nil {
		log.Fatalf("Failed to load configuration: %v", err)
	}

	if err := cfg.Validate(); err != nil {
		log.Fatalf("Invalid configuration: %v", err)
	}

	// Log configuration
	log.Printf("Log level: %s", cfg.LogLevel)
	verbose := cfg.IsVerbose()

	// Generate session ID
	sessionID, err := verify.GenerateSessionID(16)
	if err != nil {
		log.Fatalf("Failed to generate session ID: %v", err)
	}
	log.Printf("Generated session ID: %s", sessionID)

	// Register with the hub
	verifyClient := verify.NewClient(cfg.Verify.URL, cfg.Verify.APIKey, verbose)
	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	log.Printf("Registering with hub at %s", cfg.Verify.URL)
	log.Printf("Tunnel URL: %s/shadow", cfg.Verify.TunnelURL)

	if err := verifyClient.Register(ctx, cfg.Verify.TunnelURL+"/shadow", sessionID); err != nil {
		log.Fatalf("Failed to register with hub: %v", err)
	}
	log.Printf("Successfully registered with hub")

	// Initialize infrastructure
	var llmProvider domain.LLMProvider
	switch cfg.LLM.Provider {
	case "openrouter":
		llmProvider = llm.NewOpenRouterProvider(cfg.LLM.APIKey, cfg.LLM.Model, verbose)
		log.Printf("Using OpenRouter with model: %s", cfg.LLM.Model)
	case "lmstudio":
		llmProvider = llm.NewLMStudioProvider(cfg.LLM.BaseURL, cfg.LLM.Model, verbose)
		log.Printf("Using LM Studio at %s with model: %s", cfg.LLM.BaseURL, cfg.LLM.Model)
	default:
		log.Fatalf("Unknown LLM provider: %s", cfg.LLM.Provider)
	}

	sessionStorage := session.NewFileStorage(cfg.CacheDir)
	packageClient := packages.NewAPIClient(cfg.PackageAPI.BaseURL, cfg.PackageAPI.APIKey, verbose)
	weatherClient := weather.NewAPIClient(cfg.WeatherAPI.BaseURL, cfg.WeatherAPI.APIKey)

	// Initialize use case
	conversationUC := usecase.NewConversationUseCase(llmProvider, sessionStorage, packageClient, weatherClient)

	// Initialize API handler
	handler := api.NewHandler(conversationUC)

	// Setup Gin router
	router := gin.Default()

	// Add verbose logging middleware
	router.Use(api.VerboseLoggingMiddleware(verbose))

	// Register routes
	router.POST("/shadow", handler.Shadow)

	// Start server
	log.Printf("Starting server on port %s", cfg.Server.Port)
	log.Printf("Using LLM model: %s", cfg.LLM.Model)
	log.Printf("Cache directory: %s", cfg.CacheDir)

	if err := router.Run(":" + cfg.Server.Port); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

// setupLogging configures logging to both console and file
func setupLogging() error {
	// Create logs directory if it doesn't exist
	logsDir := "logs"
	if err := os.MkdirAll(logsDir, 0755); err != nil {
		return fmt.Errorf("creating logs directory: %w", err)
	}

	// Create log file with timestamp
	timestamp := time.Now().Format("2006-01-02_15-04-05")
	logFile := filepath.Join(logsDir, fmt.Sprintf("app_%s.log", timestamp))

	file, err := os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
	if err != nil {
		return fmt.Errorf("opening log file: %w", err)
	}

	// Setup multi-writer to log to both console and file
	multiWriter := io.MultiWriter(os.Stdout, file)
	log.SetOutput(multiWriter)
	log.SetFlags(log.Ldate | log.Ltime | log.Lmicroseconds | log.Lshortfile)

	log.Printf("Logging to file: %s", logFile)

	return nil
}
