package server

import (
	"embed"
	"io/fs"
	"net/http"
	"os"
	"path/filepath"

	"ci/internal/handler"
	"ci/internal/service"
	"ci/internal/store"
	"ci/internal/workspace"

	"github.com/gin-gonic/gin"
)

// Config holds server configuration.
type Config struct {
	Port    string
	DataDir string
}

// DefaultConfig returns the default server configuration.
func DefaultConfig() Config {
	return Config{
		Port:    "8080",
		DataDir: "data",
	}
}

// Server wraps the Gin engine and related services.
type Server struct {
	engine *gin.Engine
	config Config
	svc    *service.ProjectService
}

// New creates a new Server with the given config and embedded frontend.
// Pass nil for frontendFS when using local file serving (dev mode).
func New(config Config, frontendFS *embed.FS) (*Server, error) {
	// Resolve absolute data directory
	absDataDir, err := filepath.Abs(config.DataDir)
	if err != nil {
		absDataDir = config.DataDir
	}

	// Initialize store
	st, err := store.New(absDataDir)
	if err != nil {
		return nil, err
	}

	// Initialize workspace
	workspaceDir := filepath.Join(absDataDir, "workspaces")
	wm, err := workspace.New(workspaceDir)
	if err != nil {
		return nil, err
	}

	// Initialize service
	svc := service.NewProjectService(st, wm)

	// Initialize handlers
	projectH := handler.NewProjectHandler(svc)
	buildH := handler.NewBuildHandler(svc)
	uploadH := handler.NewUploadHandler(svc)
	deployH := handler.NewDeployHandler(svc)

	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()

	// CORS middleware for development
	r.Use(func(c *gin.Context) {
		c.Header("Access-Control-Allow-Origin", "*")
		c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusNoContent)
			return
		}
		c.Next()
	})

	// Health check
	r.GET("/api/health", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{"status": "ok"})
	})

	// API routes
	api := r.Group("/api")
	{
		// Projects CRUD
		api.GET("/projects", projectH.ListProjects)
		api.POST("/projects", projectH.CreateProject)
		api.GET("/projects/:id", projectH.GetProject)
		api.PUT("/projects/:id", projectH.UpdateProject)
		api.DELETE("/projects/:id", projectH.DeleteProject)

		// Build & Run
		api.POST("/projects/:id/build", buildH.TriggerBuild)
		api.POST("/projects/:id/start", buildH.StartProject)
		api.POST("/projects/:id/stop", buildH.StopProject)
		api.POST("/projects/:id/restart", buildH.RestartProject)
		api.GET("/projects/:id/builds", buildH.GetBuilds)
		api.GET("/projects/:id/logs/:buildId", buildH.GetBuildLog)
		api.GET("/projects/:id/logs/stream", buildH.StreamLogs)

		// Deploy config
		api.GET("/projects/:id/deploy-config", deployH.GetDeployConfig)
		api.PUT("/projects/:id/deploy-config", deployH.UpdateDeployConfig)
		api.DELETE("/projects/:id/deploy-config", deployH.DeleteDeployConfig)

		// Deploy execution
		api.POST("/projects/:id/deploy", deployH.TriggerDeploy)
		api.GET("/projects/:id/deploy-records", deployH.GetDeployRecords)
		api.GET("/projects/:id/deploy-records/:did/log", deployH.GetDeployLog)

		// Upload
		api.POST("/projects/:id/upload", uploadH.UploadFiles)

		// Webhook
		api.POST("/webhook/:id", buildH.Webhook)
	}

	// Serve frontend
	if frontendFS != nil {
		// Embedded mode: serve from embedded dist files
		distFS, err := fs.Sub(frontendFS, "web/dist")
		if err != nil {
			// Try without the prefix
			distFS = frontendFS
		}

		staticFS, err := fs.Sub(distFS, ".")
		_ = staticFS
		_ = err

		// Use gin's static file serving with embedded FS
		r.NoRoute(gin.WrapH(http.FileServer(http.FS(distFS))))
	} else {
		// Dev mode: try serving from web/dist directory
		localDist := "web/dist"
		if _, err := os.Stat(localDist); err == nil {
			r.NoRoute(func(c *gin.Context) {
				path := c.Request.URL.Path
				if path == "/" {
					c.File(filepath.Join(localDist, "index.html"))
					return
				}
				// Try the specific file first
				targetPath := filepath.Join(localDist, path)
				if _, err := os.Stat(targetPath); err == nil {
					c.File(targetPath)
					return
				}
				// SPA fallback
				c.File(filepath.Join(localDist, "index.html"))
			})
		} else {
			r.NoRoute(func(c *gin.Context) {
				c.String(http.StatusOK, "CI/CD Server is running. Frontend not yet built — run `cd web && npm run build`.")
			})
		}
	}

	srv := &Server{
		engine: r,
		config: config,
		svc:    svc,
	}

	return srv, nil
}

// Run starts the HTTP server.
func (s *Server) Run() error {
	return s.engine.Run(":" + s.config.Port)
}

// Shutdown gracefully shuts down the server.
func (s *Server) Shutdown() {
	s.svc.Shutdown()
}
