package handlers

// workflow_packages.go — v0.30.2 CS1
//
// Handles workflow-specific package operations:
//   - ExportWorkflowPackage: bundles a workflow definition + stages into a .pkg
//   - InstallWorkflowFromManifest: creates/updates a workflow from a .pkg manifest

import (
	"archive/zip"
	"encoding/json"
	"fmt"
	"log"
	"net/http"

	"github.com/gin-gonic/gin"

	"switchboard-core/models"
	"switchboard-core/store"
)

// WorkflowPackageHandler handles workflow package export and install.
type WorkflowPackageHandler struct {
	stores store.Stores
}

// NewWorkflowPackageHandler creates a new workflow package handler.
func NewWorkflowPackageHandler(s store.Stores) *WorkflowPackageHandler {
	return &WorkflowPackageHandler{stores: s}
}

// ExportWorkflowPackage exports a workflow definition as a downloadable .pkg.
// GET /api/v1/admin/workflows/:id/export
func (h *WorkflowPackageHandler) ExportWorkflowPackage(c *gin.Context) {
	ctx := c.Request.Context()
	wfID := c.Param("id")

	wf, err := h.stores.Workflows.GetByID(ctx, wfID)
	if err != nil || wf == nil {
		c.JSON(http.StatusNotFound, gin.H{"error": "workflow not found"})
		return
	}

	stages, err := h.stores.Workflows.ListStages(ctx, wfID)
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load stages"})
		return
	}

	// Build stage definitions for the manifest
	stageDefs := make([]map[string]any, 0, len(stages))
	for _, s := range stages {
		sd := map[string]any{
			"name":            s.Name,
			"ordinal":         s.Ordinal,
			"stage_mode":      s.StageMode,
			"audience":        s.Audience,
			"stage_type":      s.StageType,
			"auto_transition": s.AutoTransition,
		}
		if s.AssignmentTeamID != nil {
			sd["assignment_team_id"] = *s.AssignmentTeamID
		}
		if s.SurfacePkgID != nil {
			sd["surface_pkg_id"] = *s.SurfacePkgID
		}
		if s.StarlarkHook != nil {
			sd["starlark_hook"] = *s.StarlarkHook
		}
		if s.SLASeconds != nil {
			sd["sla_seconds"] = *s.SLASeconds
		}
		if len(s.FormTemplate) > 0 && string(s.FormTemplate) != "{}" {
			var ft any
			if json.Unmarshal(s.FormTemplate, &ft) == nil {
				sd["form_template"] = ft
			}
		}
		if len(s.StageConfig) > 0 && string(s.StageConfig) != "{}" {
			var sc any
			if json.Unmarshal(s.StageConfig, &sc) == nil {
				sd["stage_config"] = sc
			}
		}
		if len(s.BranchRules) > 0 && string(s.BranchRules) != "[]" {
			var br any
			if json.Unmarshal(s.BranchRules, &br) == nil {
				sd["branch_rules"] = br
			}
		}
		stageDefs = append(stageDefs, sd)
	}

	// Build the manifest
	manifest := map[string]any{
		"id":    wf.Slug,
		"title": wf.Name,
		"type":  "workflow",
		"workflow_definition": map[string]any{
			"name":       wf.Name,
			"slug":       wf.Slug,
			"entry_mode": wf.EntryMode,
			"stages":     stageDefs,
		},
	}
	if wf.Description != "" {
		manifest["description"] = wf.Description
	}

	// Include branding if set
	if len(wf.Branding) > 0 && string(wf.Branding) != "{}" {
		var branding any
		if json.Unmarshal(wf.Branding, &branding) == nil {
			wfDef := manifest["workflow_definition"].(map[string]any)
			wfDef["branding"] = branding
		}
	}

	// Set response headers
	filename := fmt.Sprintf("%s.pkg", wf.Slug)
	c.Header("Content-Type", "application/zip")
	c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))

	// Create zip writer directly to response
	zw := zip.NewWriter(c.Writer)
	defer zw.Close()

	// Write manifest.json
	manifestJSON, err := json.MarshalIndent(manifest, "", "  ")
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to serialize manifest"})
		return
	}
	mf, err := zw.Create("manifest.json")
	if err != nil {
		return
	}
	mf.Write(manifestJSON)
}

// InstallWorkflowFromManifest creates or updates a workflow from a package manifest.
// Called from InstallPackage when type="workflow".
func InstallWorkflowFromManifest(ctx *gin.Context, stores store.Stores, pkgID string, manifest map[string]any) error {
	wfDefRaw, ok := manifest["workflow_definition"]
	if !ok {
		return fmt.Errorf("missing workflow_definition in manifest")
	}

	// Re-marshal and parse for type safety
	wfDefJSON, err := json.Marshal(wfDefRaw)
	if err != nil {
		return fmt.Errorf("invalid workflow_definition: %w", err)
	}

	var wfDef struct {
		Name       string           `json:"name"`
		Slug       string           `json:"slug"`
		EntryMode  string           `json:"entry_mode"`
		Branding   json.RawMessage  `json:"branding"`
		Stages     []workflowPkgStage `json:"stages"`
	}
	if err := json.Unmarshal(wfDefJSON, &wfDef); err != nil {
		return fmt.Errorf("invalid workflow_definition: %w", err)
	}

	if wfDef.Name == "" || wfDef.Slug == "" {
		return fmt.Errorf("workflow_definition requires name and slug")
	}
	if wfDef.EntryMode == "" {
		wfDef.EntryMode = "public_link"
	}

	userID := ctx.GetString("user_id")
	reqCtx := ctx.Request.Context()

	// Check if workflow already exists (by slug, global scope)
	existing, _ := stores.Workflows.GetBySlug(reqCtx, nil, wfDef.Slug)

	var workflowID string

	if existing != nil {
		// Update existing workflow
		workflowID = existing.ID
		patch := models.WorkflowPatch{
			Name:      &wfDef.Name,
			EntryMode: &wfDef.EntryMode,
		}
		if len(wfDef.Branding) > 0 {
			b := json.RawMessage(wfDef.Branding)
			patch.Branding = &b
		}
		if err := stores.Workflows.Update(reqCtx, workflowID, patch); err != nil {
			return fmt.Errorf("failed to update workflow: %w", err)
		}

		// Delete existing stages (will be replaced)
		existingStages, _ := stores.Workflows.ListStages(reqCtx, workflowID)
		for _, s := range existingStages {
			stores.Workflows.DeleteStage(reqCtx, s.ID)
		}
	} else {
		// Create new workflow
		wf := &models.Workflow{
			Name:      wfDef.Name,
			Slug:      wfDef.Slug,
			EntryMode: wfDef.EntryMode,
			CreatedBy: userID,
		}
		if len(wfDef.Branding) > 0 {
			wf.Branding = json.RawMessage(wfDef.Branding)
		}
		if err := stores.Workflows.Create(reqCtx, wf); err != nil {
			return fmt.Errorf("failed to create workflow: %w", err)
		}
		workflowID = wf.ID
	}

	// Create stages from definition
	for _, s := range wfDef.Stages {
		st := &models.WorkflowStage{
			WorkflowID:       workflowID,
			Ordinal:          s.Ordinal,
			Name:             s.Name,
			StageMode:        s.StageMode,
			Audience:         s.Audience,
			StageType:        s.StageType,
			AutoTransition:   s.AutoTransition,
			AssignmentTeamID: s.AssignmentTeamID,
			SurfacePkgID:     s.SurfacePkgID,
			StarlarkHook:     s.StarlarkHook,
			SLASeconds:       s.SLASeconds,
		}
		if st.StageMode == "" {
			st.StageMode = models.StageModeDelegated
		}
		if st.Audience == "" {
			st.Audience = models.AudienceTeam
		}
		if st.StageType == "" {
			st.StageType = models.StageTypeSimple
		}
		if s.FormTemplate != nil {
			st.FormTemplate, _ = json.Marshal(s.FormTemplate)
		}
		if s.StageConfig != nil {
			st.StageConfig, _ = json.Marshal(s.StageConfig)
		}
		if s.BranchRules != nil {
			st.BranchRules, _ = json.Marshal(s.BranchRules)
		}
		if err := stores.Workflows.CreateStage(reqCtx, st); err != nil {
			log.Printf("[workflow-pkg] failed to create stage %q: %v", s.Name, err)
		}
	}

	// Store workflow_id in package_settings for reference
	settingsJSON, _ := json.Marshal(map[string]string{"workflow_id": workflowID})
	stores.Packages.SetPackageSettings(reqCtx, pkgID, json.RawMessage(settingsJSON))

	log.Printf("[workflow-pkg] installed workflow %q (id=%s) from package %s", wfDef.Name, workflowID, pkgID)
	return nil
}

// workflowPkgStage is the stage definition within a workflow package manifest.
type workflowPkgStage struct {
	Name             string  `json:"name"`
	Ordinal          int     `json:"ordinal"`
	StageMode        string  `json:"stage_mode"`
	Audience         string  `json:"audience"`
	StageType        string  `json:"stage_type"`
	AutoTransition   bool    `json:"auto_transition"`
	AssignmentTeamID *string `json:"assignment_team_id,omitempty"`
	SurfacePkgID     *string `json:"surface_pkg_id,omitempty"`
	StarlarkHook     *string `json:"starlark_hook,omitempty"`
	SLASeconds       *int    `json:"sla_seconds,omitempty"`
	FormTemplate     any     `json:"form_template,omitempty"`
	StageConfig      any     `json:"stage_config,omitempty"`
	BranchRules      any     `json:"branch_rules,omitempty"`
}
