package handler

import (
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"io"
	"mime"
	"net/http"
	"strings"
	"time"

	"cyberstrike-ai/internal/database"
	"cyberstrike-ai/internal/security"
	workflowrunner "cyberstrike-ai/internal/workflow"
	workflowpkg "cyberstrike-ai/internal/workflow/package"

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

type workflowPackageResolution struct {
	Action        string `json:"action"`
	NewWorkflowID string `json:"new_workflow_id"`
}
type workflowPackageImportRequest struct {
	InspectionID     string                    `json:"inspection_id"`
	Resolution       workflowPackageResolution `json:"resolution"`
	ConfirmOverwrite bool                      `json:"confirm_overwrite"`
}

func (h *WorkflowHandler) ExportPackage(c *gin.Context) {
	wf, err := h.db.GetWorkflowDefinition(c.Param("id"))
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_EXPORT_FAILED", "导出工作流包失败", nil)
		return
	}
	if wf == nil {
		writeWorkflowPackageError(c, http.StatusNotFound, "WFPKG_WORKFLOW_NOT_FOUND", "工作流不存在", nil)
		return
	}
	pkg, meta, err := workflowpkg.Export(workflowPackageDocument(wf))
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_EXPORT_FAILED", "导出工作流包失败", nil)
		return
	}
	c.Header("Content-Type", "application/zip")
	c.Header("Content-Disposition", mime.FormatMediaType("attachment", map[string]string{"filename": meta.FileName}))
	c.Header("ETag", `"`+meta.PackageHash+`"`)
	c.Header("X-Workflow-Package-SHA256", meta.PackageHash)
	if h.audit != nil {
		h.audit.RecordOK(c, "workflow_package", "export", "导出工作流包", "workflow", wf.ID, map[string]interface{}{"package_hash": meta.PackageHash})
	}
	c.Data(http.StatusOK, "application/zip", pkg)
}

func (h *WorkflowHandler) CreatePackageInspection(c *gin.Context) {
	session, ok := security.CurrentSession(c)
	if !ok || strings.TrimSpace(session.UserID) == "" {
		writeWorkflowPackageError(c, http.StatusUnauthorized, "WFPKG_INSPECTION_NOT_FOUND", "未授权访问", nil)
		return
	}
	c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, workflowpkg.MaxArchiveBytes+1)
	file, _, err := c.Request.FormFile("file")
	if err != nil {
		var maxErr *http.MaxBytesError
		if errors.As(err, &maxErr) {
			writeWorkflowPackageError(c, http.StatusUnprocessableEntity, "WFPKG_FILE_TOO_LARGE", "工作流包文件超过大小限制", nil)
			return
		}
		writeWorkflowPackageError(c, http.StatusUnprocessableEntity, "WFPKG_FILE_REQUIRED", "必须上传工作流包文件", nil)
		return
	}
	defer file.Close()
	archive, err := io.ReadAll(io.LimitReader(file, workflowpkg.MaxArchiveBytes+1))
	if err != nil || len(archive) > workflowpkg.MaxArchiveBytes {
		writeWorkflowPackageError(c, http.StatusUnprocessableEntity, "WFPKG_FILE_TOO_LARGE", "工作流包文件超过大小限制", nil)
		return
	}
	inspected, err := workflowpkg.InspectArchive(c.Request.Context(), archive, workflowrunner.ValidateGraphJSON)
	if err != nil {
		code := workflowpkg.ErrorCode(err)
		if code == "" {
			code = "WFPKG_INVALID_ARCHIVE"
		}
		if h.audit != nil {
			h.audit.RecordFail(c, "workflow_package", "inspect", "工作流包预检失败", map[string]interface{}{"code": code, "package_hash": workflowPackageHash(archive)})
		}
		writeWorkflowPackageError(c, http.StatusUnprocessableEntity, code, "工作流包预检失败", nil)
		return
	}
	state, local, err := h.workflowPackageConflict(inspected.Document.ID, inspected.ContentHash)
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "读取本地工作流失败", nil)
		return
	}
	summary := workflowPackageInspectionSummary{ID: "wpi_" + strings.ReplaceAll(uuid.NewString(), "-", ""), Status: "ready", ExpiresAt: time.Now().UTC().Add(30 * time.Minute), Package: workflowPackagePackageSummary{PackageFormat: inspected.Manifest.PackageFormat, FormatVersion: inspected.Manifest.FormatVersion, PackageID: inspected.Manifest.PackageID, PackageHash: inspected.PackageHash}, Workflow: workflowPackageWorkflowSummary{SourceID: inspected.Document.ID, Name: inspected.Document.Name, Description: inspected.Document.Description, SourceRevision: inspected.Document.Version, Enabled: inspected.Document.Enabled, ContentHash: inspected.ContentHash, GraphHash: inspected.GraphHash, NodeCount: inspected.NodeCount, EdgeCount: inspected.EdgeCount}, Conflict: workflowPackageConflictSummary{State: state}, Warnings: []string{}}
	if local != nil {
		content, graph, _, _ := workflowpkg.DocumentHashes(workflowPackageDocument(local))
		summary.Conflict.LocalWorkflow = &workflowPackageLocalWorkflow{ID: local.ID, Version: local.Version, ContentHash: content, GraphHash: graph}
	}
	manifestJSON, _ := json.Marshal(inspected.Manifest)
	payloadJSON, err := json.Marshal(inspected.Document)
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "保存预检失败", nil)
		return
	}
	inspectionJSON, _ := json.Marshal(summary)
	record := &database.WorkflowPackageInspection{ID: summary.ID, PackageHash: inspected.PackageHash, ManifestJSON: string(manifestJSON), WorkflowPayloadJSON: string(payloadJSON), InspectionJSON: string(inspectionJSON), SourceWorkflowID: inspected.Document.ID, SourceRevision: inspected.Document.Version, SourceContentHash: inspected.ContentHash, SourceGraphHash: inspected.GraphHash, LocalConflictState: state, CreatedBy: session.UserID, CreatedAt: time.Now().UTC(), ExpiresAt: summary.ExpiresAt}
	if local != nil {
		content, graph, _, _ := workflowpkg.DocumentHashes(workflowPackageDocument(local))
		record.LocalWorkflowID = local.ID
		record.LocalContentHash = content
		record.LocalGraphHash = graph
	}
	if err := h.db.CreateWorkflowPackageInspection(record); err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "保存预检失败", nil)
		return
	}
	if h.audit != nil {
		h.audit.RecordOK(c, "workflow_package", "inspect", "工作流包预检成功", "inspection", record.ID, map[string]interface{}{"package_hash": record.PackageHash, "workflow_id": record.SourceWorkflowID})
	}
	c.JSON(http.StatusCreated, gin.H{"inspection": summary})
}

func (h *WorkflowHandler) GetPackageInspection(c *gin.Context) {
	session, ok := security.CurrentSession(c)
	if !ok {
		writeWorkflowPackageError(c, http.StatusUnauthorized, "WFPKG_INSPECTION_NOT_FOUND", "未授权访问", nil)
		return
	}
	v, err := h.db.GetWorkflowPackageInspection(c.Param("inspectionId"), session.UserID)
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "读取预检失败", nil)
		return
	}
	if v == nil {
		writeWorkflowPackageError(c, http.StatusNotFound, "WFPKG_INSPECTION_NOT_FOUND", "预检不存在", nil)
		return
	}
	if v.Status == "expired" {
		writeWorkflowPackageError(c, http.StatusConflict, "WFPKG_INSPECTION_EXPIRED", "预检已过期", nil)
		return
	}
	var summary any
	_ = json.Unmarshal([]byte(v.InspectionJSON), &summary)
	c.JSON(http.StatusOK, gin.H{"inspection": summary})
}

func (h *WorkflowHandler) ApplyPackageImport(c *gin.Context) {
	session, ok := security.CurrentSession(c)
	if !ok {
		writeWorkflowPackageError(c, http.StatusUnauthorized, "WFPKG_INSPECTION_NOT_FOUND", "未授权访问", nil)
		return
	}
	key := strings.TrimSpace(c.GetHeader("Idempotency-Key"))
	if _, err := uuid.Parse(key); err != nil {
		writeWorkflowPackageError(c, http.StatusBadRequest, "WFPKG_IDEMPOTENCY_KEY_REQUIRED", "必须提供 UUID 幂等键", nil)
		return
	}
	var req workflowPackageImportRequest
	if err := c.ShouldBindJSON(&req); err != nil {
		writeWorkflowPackageError(c, http.StatusUnprocessableEntity, "WFPKG_INVALID_ACTION", "导入请求无效", nil)
		return
	}
	req.InspectionID = strings.TrimSpace(req.InspectionID)
	req.Resolution.Action = strings.TrimSpace(req.Resolution.Action)
	req.Resolution.NewWorkflowID = strings.TrimSpace(req.Resolution.NewWorkflowID)
	if req.Resolution.Action != "rename" && req.Resolution.NewWorkflowID != "" {
		writeWorkflowPackageError(c, http.StatusUnprocessableEntity, "WFPKG_INVALID_ACTION", "当前导入动作不接受新工作流 ID", nil)
		return
	}
	requestHash := workflowPackageRequestHash(req)
	imp, replayed, err := h.db.ApplyWorkflowPackageImport(c.Request.Context(), database.WorkflowPackageApplyRequest{InspectionID: req.InspectionID, RequestHash: requestHash, IdempotencyKey: key, ActorUserID: session.UserID, Action: req.Resolution.Action, NewWorkflowID: req.Resolution.NewWorkflowID, ConfirmOverwrite: req.ConfirmOverwrite})
	if err != nil {
		h.writeWorkflowPackageImportError(c, req.InspectionID, err)
		return
	}
	wf, _ := h.db.GetWorkflowDefinition(imp.ResultingWorkflowID)
	if !replayed && (imp.Result == "created" || imp.Result == "overwritten" || imp.Result == "renamed") {
		workflowrunner.InvalidateCompiledCache(imp.ResultingWorkflowID)
	}
	response := h.workflowPackageImportResponse(imp, wf)
	if !replayed && h.audit != nil {
		h.audit.RecordOK(c, "workflow_package", "import", "工作流包导入成功", "workflow", imp.ResultingWorkflowID, map[string]interface{}{"inspection_id": imp.InspectionID, "action": imp.Action, "result": imp.Result})
	}
	status := http.StatusCreated
	if replayed {
		status = http.StatusOK
	}
	c.JSON(status, gin.H{"import": response})
}

func (h *WorkflowHandler) GetPackageImport(c *gin.Context) {
	session, ok := security.CurrentSession(c)
	if !ok {
		writeWorkflowPackageError(c, http.StatusUnauthorized, "WFPKG_INSPECTION_NOT_FOUND", "未授权访问", nil)
		return
	}
	imp, err := h.db.GetWorkflowPackageImport(c.Param("importId"), session.UserID)
	if err != nil {
		writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "读取导入结果失败", nil)
		return
	}
	if imp == nil {
		writeWorkflowPackageError(c, http.StatusNotFound, "WFPKG_INSPECTION_NOT_FOUND", "导入结果不存在", nil)
		return
	}
	wf, _ := h.db.GetWorkflowDefinition(imp.ResultingWorkflowID)
	c.JSON(http.StatusOK, gin.H{"import": h.workflowPackageImportResponse(imp, wf)})
}

func (h *WorkflowHandler) workflowPackageConflict(id, sourceHash string) (string, *database.WorkflowDefinition, error) {
	local, err := h.db.GetWorkflowDefinition(id)
	if err != nil {
		return "", nil, err
	}
	if local == nil {
		return "none", nil, nil
	}
	localHash, _, _, err := workflowpkg.DocumentHashes(workflowPackageDocument(local))
	if err != nil {
		return "", nil, err
	}
	if localHash == sourceHash {
		return "identical", local, nil
	}
	return "id_conflict", local, nil
}
func workflowPackageDocument(w *database.WorkflowDefinition) workflowpkg.Document {
	return workflowpkg.Document{ID: w.ID, Name: w.Name, Description: w.Description, Version: w.Version, GraphJSON: w.GraphJSON, Enabled: w.Enabled, UpdatedAt: w.UpdatedAt}
}
func workflowPackageRequestHash(req workflowPackageImportRequest) string {
	value := struct {
		ConfirmOverwrite bool                      `json:"confirm_overwrite"`
		InspectionID     string                    `json:"inspection_id"`
		Resolution       workflowPackageResolution `json:"resolution"`
	}{req.ConfirmOverwrite, req.InspectionID, req.Resolution}
	b, _ := json.Marshal(value)
	sum := sha256.Sum256(b)
	return "sha256:" + hex.EncodeToString(sum[:])
}
func workflowPackageHash(b []byte) string {
	sum := sha256.Sum256(b)
	return "sha256:" + hex.EncodeToString(sum[:])
}

func (h *WorkflowHandler) writeWorkflowPackageImportError(c *gin.Context, inspectionID string, err error) {
	var e *database.WorkflowPackageStoreError
	if errors.As(err, &e) {
		status := http.StatusConflict
		if e.Code == "WFPKG_INVALID_ACTION" || e.Code == "WFPKG_INVALID_RENAME_ID" {
			status = http.StatusUnprocessableEntity
		}
		if h.audit != nil {
			h.audit.RecordFail(c, "workflow_package", "import", "工作流包导入失败", map[string]interface{}{"code": e.Code, "inspection_id": inspectionID})
		}
		writeWorkflowPackageError(c, status, e.Code, e.Message, nil)
		return
	}
	if h.audit != nil {
		h.audit.RecordFail(c, "workflow_package", "import", "工作流包导入失败", map[string]interface{}{"code": "WFPKG_IMPORT_FAILED", "inspection_id": inspectionID})
	}
	writeWorkflowPackageError(c, http.StatusInternalServerError, "WFPKG_IMPORT_FAILED", "导入工作流包失败", nil)
}
func writeWorkflowPackageError(c *gin.Context, status int, code, message string, details map[string]any) {
	body := gin.H{"code": code, "message": message}
	if len(details) > 0 {
		body["details"] = details
	}
	c.JSON(status, gin.H{"error": body})
}

type workflowPackageInspectionSummary struct {
	ID        string                         `json:"id"`
	Status    string                         `json:"status"`
	ExpiresAt time.Time                      `json:"expires_at"`
	Package   workflowPackagePackageSummary  `json:"package"`
	Workflow  workflowPackageWorkflowSummary `json:"workflow"`
	Conflict  workflowPackageConflictSummary `json:"conflict"`
	Warnings  []string                       `json:"warnings"`
}
type workflowPackagePackageSummary struct {
	PackageFormat string `json:"package_format"`
	FormatVersion string `json:"format_version"`
	PackageID     string `json:"package_id"`
	PackageHash   string `json:"package_hash"`
}
type workflowPackageWorkflowSummary struct {
	SourceID       string `json:"source_id"`
	Name           string `json:"name"`
	Description    string `json:"description"`
	SourceRevision int    `json:"source_revision"`
	Enabled        bool   `json:"enabled"`
	ContentHash    string `json:"content_hash"`
	GraphHash      string `json:"graph_hash"`
	NodeCount      int    `json:"node_count"`
	EdgeCount      int    `json:"edge_count"`
}
type workflowPackageConflictSummary struct {
	State         string                        `json:"state"`
	LocalWorkflow *workflowPackageLocalWorkflow `json:"local_workflow,omitempty"`
}
type workflowPackageLocalWorkflow struct {
	ID          string `json:"id"`
	Version     int    `json:"version"`
	ContentHash string `json:"content_hash"`
	GraphHash   string `json:"graph_hash"`
}

func (h *WorkflowHandler) workflowPackageImportResponse(imp *database.WorkflowPackageImport, wf *database.WorkflowDefinition) gin.H {
	out := gin.H{"id": imp.ID, "inspection_id": imp.InspectionID, "status": "succeeded", "result": imp.Result, "action": imp.Action, "source_workflow_id": imp.SourceWorkflowID, "target_workflow_id": imp.TargetWorkflowID, "applied_at": imp.AppliedAt}
	if wf != nil {
		content, graph, _, _ := workflowpkg.DocumentHashes(workflowPackageDocument(wf))
		out["workflow"] = gin.H{"id": wf.ID, "version": wf.Version, "content_hash": content, "graph_hash": graph}
	}
	return out
}
