package database

import (
	"context"
	"crypto/sha256"
	"database/sql"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"time"
	"unicode"

	"github.com/google/uuid"
)

type WorkflowPackageInspection struct {
	ID, PackageHash, ManifestJSON, WorkflowPayloadJSON, InspectionJSON    string
	SourceWorkflowID, SourceContentHash, SourceGraphHash                  string
	SourceRevision                                                        int
	LocalConflictState, LocalWorkflowID, LocalContentHash, LocalGraphHash string
	CreatedBy, Status                                                     string
	CreatedAt, ExpiresAt                                                  time.Time
	ConsumedAt                                                            *time.Time
}

type WorkflowPackageImport struct {
	ID, InspectionID, RequestHash, IdempotencyKey, ActorUserID      string
	Action, SourceWorkflowID, TargetWorkflowID, ResultingWorkflowID string
	Result, ErrorCode, ErrorMessage                                 string
	CreatedAt                                                       time.Time
	AppliedAt                                                       *time.Time
}

type WorkflowPackageApplyRequest struct {
	InspectionID, RequestHash, IdempotencyKey, ActorUserID, Action, NewWorkflowID string
	ConfirmOverwrite                                                              bool
}

type WorkflowPackageStoreError struct{ Code, Message string }

func (e *WorkflowPackageStoreError) Error() string { return e.Code + ": " + e.Message }
func workflowPackageStoreError(code, message string) error {
	return &WorkflowPackageStoreError{code, message}
}

func (db *DB) CreateWorkflowPackageInspection(v *WorkflowPackageInspection) error {
	if v == nil || strings.TrimSpace(v.ID) == "" || strings.TrimSpace(v.CreatedBy) == "" {
		return fmt.Errorf("workflow package inspection is incomplete")
	}
	if v.CreatedAt.IsZero() {
		v.CreatedAt = time.Now().UTC()
	}
	if v.ExpiresAt.IsZero() {
		v.ExpiresAt = v.CreatedAt.Add(30 * time.Minute)
	}
	_, err := db.Exec(`INSERT INTO workflow_package_inspections (id,package_hash,manifest_json,workflow_payload_json,inspection_json,source_workflow_id,source_revision,source_content_hash,source_graph_hash,local_conflict_state,local_workflow_id,local_content_hash,local_graph_hash,created_by,status,created_at,expires_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`, v.ID, v.PackageHash, v.ManifestJSON, v.WorkflowPayloadJSON, v.InspectionJSON, v.SourceWorkflowID, v.SourceRevision, v.SourceContentHash, v.SourceGraphHash, v.LocalConflictState, nullString(v.LocalWorkflowID), nullString(v.LocalContentHash), nullString(v.LocalGraphHash), v.CreatedBy, "ready", v.CreatedAt.UTC(), v.ExpiresAt.UTC())
	return err
}

func (db *DB) GetWorkflowPackageInspection(id, actor string) (*WorkflowPackageInspection, error) {
	now := time.Now().UTC()
	_, _ = db.Exec(`UPDATE workflow_package_inspections SET status='expired' WHERE status='ready' AND expires_at <= ?`, now)
	row, err := scanWorkflowPackageInspection(db.QueryRow(`SELECT id,package_hash,manifest_json,workflow_payload_json,inspection_json,source_workflow_id,source_revision,source_content_hash,source_graph_hash,local_conflict_state,COALESCE(local_workflow_id,''),COALESCE(local_content_hash,''),COALESCE(local_graph_hash,''),created_by,status,created_at,expires_at,consumed_at FROM workflow_package_inspections WHERE id=? AND created_by=?`, strings.TrimSpace(id), strings.TrimSpace(actor)))
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return row, err
}

func scanWorkflowPackageInspection(s interface{ Scan(...any) error }) (*WorkflowPackageInspection, error) {
	var v WorkflowPackageInspection
	var consumed sql.NullTime
	err := s.Scan(&v.ID, &v.PackageHash, &v.ManifestJSON, &v.WorkflowPayloadJSON, &v.InspectionJSON, &v.SourceWorkflowID, &v.SourceRevision, &v.SourceContentHash, &v.SourceGraphHash, &v.LocalConflictState, &v.LocalWorkflowID, &v.LocalContentHash, &v.LocalGraphHash, &v.CreatedBy, &v.Status, &v.CreatedAt, &v.ExpiresAt, &consumed)
	if consumed.Valid {
		t := consumed.Time
		v.ConsumedAt = &t
	}
	return &v, err
}

func (db *DB) GetWorkflowPackageImport(id, actor string) (*WorkflowPackageImport, error) {
	v, err := scanWorkflowPackageImport(db.QueryRow(`SELECT id,inspection_id,request_hash,idempotency_key,actor_user_id,action,source_workflow_id,target_workflow_id,COALESCE(resulting_workflow_id,''),result,COALESCE(error_code,''),COALESCE(error_message,''),created_at,applied_at FROM workflow_package_imports WHERE id=? AND actor_user_id=?`, strings.TrimSpace(id), strings.TrimSpace(actor)))
	if err == sql.ErrNoRows {
		return nil, nil
	}
	return v, err
}
func scanWorkflowPackageImport(s interface{ Scan(...any) error }) (*WorkflowPackageImport, error) {
	var v WorkflowPackageImport
	var applied sql.NullTime
	err := s.Scan(&v.ID, &v.InspectionID, &v.RequestHash, &v.IdempotencyKey, &v.ActorUserID, &v.Action, &v.SourceWorkflowID, &v.TargetWorkflowID, &v.ResultingWorkflowID, &v.Result, &v.ErrorCode, &v.ErrorMessage, &v.CreatedAt, &applied)
	if applied.Valid {
		t := applied.Time
		v.AppliedAt = &t
	}
	return &v, err
}

func (db *DB) ApplyWorkflowPackageImport(ctx context.Context, req WorkflowPackageApplyRequest) (*WorkflowPackageImport, bool, error) {
	tx, err := db.BeginTx(ctx, nil)
	if err != nil {
		return nil, false, err
	}
	defer tx.Rollback()
	var existingHash string
	previous, prevErr := scanWorkflowPackageImport(tx.QueryRowContext(ctx, `SELECT id,inspection_id,request_hash,idempotency_key,actor_user_id,action,source_workflow_id,target_workflow_id,COALESCE(resulting_workflow_id,''),result,COALESCE(error_code,''),COALESCE(error_message,''),created_at,applied_at FROM workflow_package_imports WHERE actor_user_id=? AND idempotency_key=?`, req.ActorUserID, req.IdempotencyKey))
	if prevErr == nil {
		existingHash = previous.RequestHash
		if existingHash != req.RequestHash {
			return nil, false, workflowPackageStoreError("WFPKG_IDEMPOTENCY_KEY_REUSED", "幂等键已用于其他请求")
		}
		return previous, true, nil
	}
	if prevErr != sql.ErrNoRows {
		return nil, false, prevErr
	}
	inspection, err := scanWorkflowPackageInspection(tx.QueryRowContext(ctx, `SELECT id,package_hash,manifest_json,workflow_payload_json,inspection_json,source_workflow_id,source_revision,source_content_hash,source_graph_hash,local_conflict_state,COALESCE(local_workflow_id,''),COALESCE(local_content_hash,''),COALESCE(local_graph_hash,''),created_by,status,created_at,expires_at,consumed_at FROM workflow_package_inspections WHERE id=? AND created_by=?`, req.InspectionID, req.ActorUserID))
	if err == sql.ErrNoRows {
		return nil, false, workflowPackageStoreError("WFPKG_INSPECTION_NOT_FOUND", "预检不存在")
	}
	if err != nil {
		return nil, false, err
	}
	now := time.Now().UTC()
	if !inspection.ExpiresAt.After(now) || inspection.Status == "expired" {
		_, _ = tx.ExecContext(ctx, `UPDATE workflow_package_inspections SET status='expired' WHERE id=?`, inspection.ID)
		return nil, false, workflowPackageStoreError("WFPKG_INSPECTION_EXPIRED", "预检已过期")
	}
	if inspection.Status != "ready" {
		return nil, false, workflowPackageStoreError("WFPKG_INSPECTION_CONSUMED", "预检已被使用")
	}
	var payload struct {
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description"`
		GraphJSON   string `json:"graph_json"`
		Enabled     bool   `json:"enabled"`
	}
	if err := json.Unmarshal([]byte(inspection.WorkflowPayloadJSON), &payload); err != nil {
		return nil, false, fmt.Errorf("decode inspection payload: %w", err)
	}
	targetID := inspection.SourceWorkflowID
	if req.Action == "rename" {
		targetID = strings.TrimSpace(req.NewWorkflowID)
		if !validWorkflowPackageID(targetID) {
			return nil, false, workflowPackageStoreError("WFPKG_INVALID_RENAME_ID", "新工作流 ID 无效")
		}
	}
	sourceCurrent, err := scanWorkflowDefinition(tx.QueryRowContext(ctx, "SELECT "+workflowDefinitionColumns+" FROM workflow_definitions WHERE id=?", inspection.SourceWorkflowID))
	if err == sql.ErrNoRows {
		sourceCurrent = nil
	} else if err != nil {
		return nil, false, err
	}
	if err := checkWorkflowPackageSnapshot(inspection, sourceCurrent, inspection.SourceWorkflowID); err != nil {
		return nil, false, err
	}
	current := sourceCurrent
	if targetID != inspection.SourceWorkflowID {
		current, err = scanWorkflowDefinition(tx.QueryRowContext(ctx, "SELECT "+workflowDefinitionColumns+" FROM workflow_definitions WHERE id=?", targetID))
		if err == sql.ErrNoRows {
			current = nil
		} else if err != nil {
			return nil, false, err
		}
	}
	result := ""
	resultingID := ""
	switch req.Action {
	case "create":
		if inspection.LocalConflictState != "none" || current != nil {
			return nil, false, workflowPackageStoreError("WFPKG_ID_CONFLICT", "目标工作流已存在")
		}
		result = "created"
		resultingID = targetID
	case "keep_existing":
		if inspection.LocalConflictState == "none" {
			return nil, false, workflowPackageStoreError("WFPKG_ID_CONFLICT", "当前预检不允许保留本地")
		}
		if inspection.LocalConflictState == "identical" {
			result = "skipped_identical"
		} else {
			result = "kept_existing"
		}
		resultingID = targetID
	case "overwrite":
		if inspection.LocalConflictState != "id_conflict" {
			return nil, false, workflowPackageStoreError("WFPKG_ID_CONFLICT", "当前预检不允许覆盖")
		}
		if !req.ConfirmOverwrite {
			return nil, false, workflowPackageStoreError("WFPKG_OVERWRITE_CONFIRMATION_REQUIRED", "覆盖需要确认")
		}
		result = "overwritten"
		resultingID = targetID
	case "rename":
		if inspection.LocalConflictState != "id_conflict" || current != nil {
			return nil, false, workflowPackageStoreError("WFPKG_ID_CONFLICT", "当前预检不允许另存")
		}
		result = "renamed"
		resultingID = targetID
	default:
		return nil, false, workflowPackageStoreError("WFPKG_INVALID_ACTION", "导入动作无效")
	}
	if result == "created" || result == "renamed" {
		_, err = tx.ExecContext(ctx, `INSERT INTO workflow_definitions (id,name,description,version,graph_json,enabled,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?)`, resultingID, payload.Name, payload.Description, 1, payload.GraphJSON, boolToInt(payload.Enabled), now, now)
	} else if result == "overwritten" {
		_, err = tx.ExecContext(ctx, `UPDATE workflow_definitions SET name=?,description=?,version=version+1,graph_json=?,enabled=?,updated_at=? WHERE id=?`, payload.Name, payload.Description, payload.GraphJSON, boolToInt(payload.Enabled), now, resultingID)
	}
	if err != nil {
		return nil, false, err
	}
	imp := &WorkflowPackageImport{ID: "wpii_" + strings.ReplaceAll(uuid.NewString(), "-", ""), InspectionID: inspection.ID, RequestHash: req.RequestHash, IdempotencyKey: req.IdempotencyKey, ActorUserID: req.ActorUserID, Action: req.Action, SourceWorkflowID: inspection.SourceWorkflowID, TargetWorkflowID: targetID, ResultingWorkflowID: resultingID, Result: result, CreatedAt: now, AppliedAt: &now}
	_, err = tx.ExecContext(ctx, `INSERT INTO workflow_package_imports (id,inspection_id,request_hash,idempotency_key,actor_user_id,action,source_workflow_id,target_workflow_id,resulting_workflow_id,result,created_at,applied_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)`, imp.ID, imp.InspectionID, imp.RequestHash, imp.IdempotencyKey, imp.ActorUserID, imp.Action, imp.SourceWorkflowID, imp.TargetWorkflowID, nullString(imp.ResultingWorkflowID), imp.Result, now, now)
	if err != nil {
		return nil, false, err
	}
	if _, err = tx.ExecContext(ctx, `UPDATE workflow_package_inspections SET status='consumed',consumed_at=? WHERE id=? AND status='ready'`, now, inspection.ID); err != nil {
		return nil, false, err
	}
	if err = tx.Commit(); err != nil {
		return nil, false, err
	}
	return imp, false, nil
}

func checkWorkflowPackageSnapshot(i *WorkflowPackageInspection, current *WorkflowDefinition, targetID string) error {
	if i.LocalConflictState == "none" {
		if current != nil {
			return workflowPackageStoreError("WFPKG_CONFLICT_CHANGED", "本地工作流已变化")
		}
		return nil
	}
	if current == nil || current.ID != i.LocalWorkflowID || current.ID != targetID {
		return workflowPackageStoreError("WFPKG_CONFLICT_CHANGED", "本地工作流已变化")
	}
	content, graph := workflowDefinitionPackageHashes(current)
	if content != i.LocalContentHash || graph != i.LocalGraphHash {
		return workflowPackageStoreError("WFPKG_CONFLICT_CHANGED", "本地工作流已变化")
	}
	return nil
}
func workflowDefinitionPackageHashes(w *WorkflowDefinition) (string, string) {
	var g any
	dec := json.NewDecoder(strings.NewReader(w.GraphJSON))
	dec.UseNumber()
	_ = dec.Decode(&g)
	graph, _ := json.Marshal(g)
	payload := struct {
		ID          string `json:"id"`
		Name        string `json:"name"`
		Description string `json:"description,omitempty"`
		Version     int    `json:"version"`
		GraphJSON   string `json:"graph_json"`
		Enabled     bool   `json:"enabled"`
	}{w.ID, w.Name, w.Description, w.Version, string(graph), w.Enabled}
	b, _ := json.Marshal(payload)
	return workflowPackageHash(b), workflowPackageHash(graph)
}
func workflowPackageHash(b []byte) string {
	s := sha256.Sum256(b)
	return "sha256:" + hex.EncodeToString(s[:])
}
func validWorkflowPackageID(id string) bool {
	if len(id) < 1 || len(id) > 128 {
		return false
	}
	for _, r := range id {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}

func (db *DB) PurgeWorkflowPackageLifecycle(now time.Time) error {
	now = now.UTC()
	if _, err := db.Exec(`UPDATE workflow_package_inspections SET status='expired' WHERE status='ready' AND expires_at<=?`, now); err != nil {
		return err
	}
	if _, err := db.Exec(`DELETE FROM workflow_package_inspections WHERE status='expired' AND expires_at<? AND NOT EXISTS (SELECT 1 FROM workflow_package_imports i WHERE i.inspection_id=workflow_package_inspections.id)`, now.Add(-24*time.Hour)); err != nil {
		return err
	}
	_, err := db.Exec(`DELETE FROM workflow_package_imports WHERE created_at<?`, now.AddDate(0, 0, -90))
	return err
}
