package iace

import (
	"bytes"
	"encoding/json"
	"strings"
	"testing"
	"time"

	"github.com/google/uuid"
)

// buildFullValidContext returns a CompletenessContext where all required gates pass
// and the project is ready for CE export.
func buildFullValidContext() *CompletenessContext {
	projectID := uuid.New()
	componentID1 := uuid.New()
	componentID2 := uuid.New()
	hazardID1 := uuid.New()
	hazardID2 := uuid.New()
	hazardID3 := uuid.New()
	mitigationID1 := uuid.New()
	mitigationID2 := uuid.New()
	mitigationID3 := uuid.New()
	now := time.Now()

	metadata, _ := json.Marshal(map[string]interface{}{
		"operating_limits":   "Temperature: -10 to 50C, Humidity: 10-90% RH",
		"foreseeable_misuse": "Use without protective equipment, exceeding load capacity",
	})

	return &CompletenessContext{
		Project: &Project{
			ID:              projectID,
			TenantID:        uuid.New(),
			MachineName:     "CNC-Fraese ProLine 5000",
			MachineType:     "cnc_milling_machine",
			Manufacturer:    "BreakPilot Maschinenbau GmbH",
			Description:     "5-Achsen CNC-Fraesmaschine fuer Praezisionsfertigung im Metallbau",
			Status:          ProjectStatusTechFile,
			CEMarkingTarget: "2023/1230",
			Metadata:        metadata,
			CreatedAt:       now,
			UpdatedAt:       now,
		},
		Components: []Component{
			{
				ID:               componentID1,
				ProjectID:        projectID,
				Name:             "Spindelantrieb",
				ComponentType:    ComponentTypeMechanical,
				IsSafetyRelevant: true,
				Description:      "Hauptspindelantrieb mit Drehzahlregelung",
				CreatedAt:        now,
				UpdatedAt:        now,
			},
			{
				ID:               componentID2,
				ProjectID:        projectID,
				Name:             "SPS Steuerung",
				ComponentType:    ComponentTypeController,
				IsSafetyRelevant: false,
				IsNetworked:      true,
				Description:      "Programmierbare Steuerung fuer Achsenbewegung",
				CreatedAt:        now,
				UpdatedAt:        now,
			},
		},
		Classifications: []RegulatoryClassification{
			{ID: uuid.New(), ProjectID: projectID, Regulation: RegulationAIAct, ClassificationResult: "Not applicable", RiskLevel: RiskLevelNegligible, Confidence: 0.95, CreatedAt: now, UpdatedAt: now},
			{ID: uuid.New(), ProjectID: projectID, Regulation: RegulationMachineryRegulation, ClassificationResult: "Annex I", RiskLevel: RiskLevelHigh, Confidence: 0.9, CreatedAt: now, UpdatedAt: now},
			{ID: uuid.New(), ProjectID: projectID, Regulation: RegulationNIS2, ClassificationResult: "Not in scope", RiskLevel: RiskLevelLow, Confidence: 0.85, CreatedAt: now, UpdatedAt: now},
			{ID: uuid.New(), ProjectID: projectID, Regulation: RegulationCRA, ClassificationResult: "Default category", RiskLevel: RiskLevelMedium, Confidence: 0.88, CreatedAt: now, UpdatedAt: now},
		},
		Hazards: []Hazard{
			{ID: hazardID1, ProjectID: projectID, ComponentID: componentID1, Name: "Quetschgefahr Spindel", Category: "mechanical", Description: "Quetschgefahr beim Werkzeugwechsel", Status: HazardStatusMitigated, CreatedAt: now, UpdatedAt: now},
			{ID: hazardID2, ProjectID: projectID, ComponentID: componentID1, Name: "Schnittverletzung", Category: "mechanical", Description: "Schnittverletzung durch rotierende Fraeser", Status: HazardStatusMitigated, CreatedAt: now, UpdatedAt: now},
			{ID: hazardID3, ProjectID: projectID, ComponentID: componentID2, Name: "Elektrischer Schlag", Category: "electrical", Description: "Kontakt mit spannungsfuehrenden Teilen", Status: HazardStatusAccepted, CreatedAt: now, UpdatedAt: now},
		},
		Assessments: []RiskAssessment{
			{ID: uuid.New(), HazardID: hazardID1, Version: 1, AssessmentType: AssessmentTypePostMitigation, Severity: 3, Exposure: 2, Probability: 2, InherentRisk: 12, ControlMaturity: 4, ControlCoverage: 0.85, TestEvidenceStrength: 0.8, CEff: 0.75, ResidualRisk: 3.0, RiskLevel: RiskLevelLow, IsAcceptable: true, AssessedBy: uuid.New(), CreatedAt: now},
			{ID: uuid.New(), HazardID: hazardID2, Version: 1, AssessmentType: AssessmentTypePostMitigation, Severity: 2, Exposure: 2, Probability: 1, InherentRisk: 4, ControlMaturity: 3, ControlCoverage: 0.9, TestEvidenceStrength: 0.7, CEff: 0.8, ResidualRisk: 0.8, RiskLevel: RiskLevelNegligible, IsAcceptable: true, AssessedBy: uuid.New(), CreatedAt: now},
			{ID: uuid.New(), HazardID: hazardID3, Version: 1, AssessmentType: AssessmentTypePostMitigation, Severity: 2, Exposure: 1, Probability: 1, InherentRisk: 2, ControlMaturity: 4, ControlCoverage: 0.95, TestEvidenceStrength: 0.9, CEff: 0.9, ResidualRisk: 0.2, RiskLevel: RiskLevelNegligible, IsAcceptable: true, AssessedBy: uuid.New(), CreatedAt: now},
		},
		Mitigations: []Mitigation{
			{ID: mitigationID1, HazardID: hazardID1, ReductionType: ReductionTypeDesign, Name: "Schutzhaube mit Verriegelung", Status: MitigationStatusVerified, VerificationMethod: VerificationMethodTest, VerificationResult: "Bestanden", CreatedAt: now, UpdatedAt: now},
			{ID: mitigationID2, HazardID: hazardID2, ReductionType: ReductionTypeProtective, Name: "Lichtschranke Arbeitsbereich", Status: MitigationStatusVerified, VerificationMethod: VerificationMethodInspection, VerificationResult: "Bestanden", CreatedAt: now, UpdatedAt: now},
			{ID: mitigationID3, HazardID: hazardID3, ReductionType: ReductionTypeInformation, Name: "Warnhinweis Hochspannung", Status: MitigationStatusVerified, VerificationMethod: VerificationMethodReview, VerificationResult: "Bestanden", CreatedAt: now, UpdatedAt: now},
		},
		Evidence: []Evidence{
			{ID: uuid.New(), ProjectID: projectID, MitigationID: &mitigationID1, FileName: "pruefbericht_schutzhaube.pdf", FilePath: "/evidence/pruefbericht_schutzhaube.pdf", FileHash: "sha256:abc123", FileSize: 524288, MimeType: "application/pdf", Description: "Pruefbericht Schutzhaubenverriegelung", UploadedBy: uuid.New(), CreatedAt: now},
			{ID: uuid.New(), ProjectID: projectID, MitigationID: &mitigationID2, FileName: "lichtschranke_abnahme.pdf", FilePath: "/evidence/lichtschranke_abnahme.pdf", FileHash: "sha256:def456", FileSize: 1048576, MimeType: "application/pdf", Description: "Abnahmeprotokoll Lichtschranke", UploadedBy: uuid.New(), CreatedAt: now},
		},
		TechFileSections: []TechFileSection{
			{ID: uuid.New(), ProjectID: projectID, SectionType: "risk_assessment_report", Title: "Risikobeurteilung nach ISO 12100", Content: "Vollstaendige Risikobeurteilung der CNC-Fraese...", Version: 1, Status: TechFileSectionStatusApproved, CreatedAt: now, UpdatedAt: now},
			{ID: uuid.New(), ProjectID: projectID, SectionType: "hazard_log_combined", Title: "Gefaehrdungsprotokoll", Content: "Protokoll aller identifizierten Gefaehrdungen...", Version: 1, Status: TechFileSectionStatusApproved, CreatedAt: now, UpdatedAt: now},
		},
		HasAI:                    false,
		PatternMatchingPerformed: true,
	}
}

// findGate searches through a CompletenessResult for a gate with the given ID.
// Returns the gate and true if found, zero-value gate and false otherwise.
func findGate(result CompletenessResult, gateID string) (CompletenessGate, bool) {
	for _, g := range result.Gates {
		if g.ID == gateID {
			return g, true
		}
	}
	return CompletenessGate{}, false
}

// ============================================================================
// Test 1: Full User Journey
// ============================================================================

func TestCEWorkflow_FullUserJourney(t *testing.T) {
	ctx := buildFullValidContext()
	checker := NewCompletenessChecker()

	// Step 1: Verify completeness check passes all required gates
	result := checker.Check(ctx)

	if !result.CanExport {
		t.Error("CanExport should be true for fully valid project")
		for _, g := range result.Gates {
			if g.Required && !g.Passed {
				t.Errorf("  Required gate %s (%s) failed: %s", g.ID, g.Label, g.Details)
			}
		}
	}

	if result.PassedRequired != result.TotalRequired {
		t.Errorf("PassedRequired = %d, TotalRequired = %d; want all required gates to pass",
			result.PassedRequired, result.TotalRequired)
	}

	// All required gates should individually pass
	for _, g := range result.Gates {
		if g.Required && !g.Passed {
			t.Errorf("Required gate %s (%s) did not pass: %s", g.ID, g.Label, g.Details)
		}
	}

	// Step 2: Export PDF and verify output
	exporter := NewDocumentExporter()

	pdfData, err := exporter.ExportPDF(
		ctx.Project,
		ctx.TechFileSections,
		ctx.Hazards,
		ctx.Assessments,
		ctx.Mitigations,
		ctx.Classifications,
	)
	if err != nil {
		t.Fatalf("ExportPDF returned error: %v", err)
	}
	if len(pdfData) == 0 {
		t.Fatal("ExportPDF returned empty bytes")
	}
	if !bytes.HasPrefix(pdfData, []byte("%PDF-")) {
		t.Errorf("PDF output does not start with %%PDF-, got first 10 bytes: %q", pdfData[:min(10, len(pdfData))])
	}

	// Step 3: Export Excel and verify output
	xlsxData, err := exporter.ExportExcel(
		ctx.Project,
		ctx.TechFileSections,
		ctx.Hazards,
		ctx.Assessments,
		ctx.Mitigations,
	)
	if err != nil {
		t.Fatalf("ExportExcel returned error: %v", err)
	}
	if len(xlsxData) == 0 {
		t.Fatal("ExportExcel returned empty bytes")
	}
	if !bytes.HasPrefix(xlsxData, []byte("PK")) {
		t.Errorf("Excel output does not start with PK (zip signature), got first 4 bytes: %x", xlsxData[:min(4, len(xlsxData))])
	}

	// Step 4: Export Markdown and verify output contains section titles
	mdData, err := exporter.ExportMarkdown(ctx.Project, ctx.TechFileSections)
	if err != nil {
		t.Fatalf("ExportMarkdown returned error: %v", err)
	}
	if len(mdData) == 0 {
		t.Fatal("ExportMarkdown returned empty bytes")
	}
	mdContent := string(mdData)
	for _, section := range ctx.TechFileSections {
		if !strings.Contains(mdContent, section.Title) {
			t.Errorf("Markdown output missing section title %q", section.Title)
		}
	}
	if !strings.Contains(mdContent, ctx.Project.MachineName) {
		t.Errorf("Markdown output missing project name %q", ctx.Project.MachineName)
	}

	// Step 5: Export DOCX and verify output
	docxData, err := exporter.ExportDOCX(ctx.Project, ctx.TechFileSections)
	if err != nil {
		t.Fatalf("ExportDOCX returned error: %v", err)
	}
	if len(docxData) == 0 {
		t.Fatal("ExportDOCX returned empty bytes")
	}
	if !bytes.HasPrefix(docxData, []byte("PK")) {
		t.Errorf("DOCX output does not start with PK (zip signature), got first 4 bytes: %x", docxData[:min(4, len(docxData))])
	}
}

// ============================================================================
// Test 2: High Risk Not Acceptable blocks export
// ============================================================================

func TestCEWorkflow_HighRiskNotAcceptable(t *testing.T) {
	ctx := buildFullValidContext()

	// Override: make one hazard high-risk and not acceptable
	hazardID := ctx.Hazards[0].ID
	ctx.Assessments[0] = RiskAssessment{
		ID:             uuid.New(),
		HazardID:       hazardID,
		Version:        2,
		AssessmentType: AssessmentTypePostMitigation,
		Severity:       5,
		Exposure:       4,
		Probability:    4,
		InherentRisk:   80,
		ControlMaturity: 2,
		ControlCoverage: 0.3,
		CEff:           0.2,
		ResidualRisk:   64,
		RiskLevel:      RiskLevelHigh,
		IsAcceptable:   false,
		AssessedBy:     uuid.New(),
		CreatedAt:      time.Now(),
	}

	checker := NewCompletenessChecker()
	result := checker.Check(ctx)

	if result.CanExport {
		t.Error("CanExport should be false when a high-risk hazard is not acceptable")
	}

	// Verify G24 (residual risk accepted) specifically fails
	g24, found := findGate(result, "G24")
	if !found {
		t.Fatal("G24 gate not found in results")
	}
	if g24.Passed {
		t.Error("G24 should fail when a hazard has RiskLevelHigh and IsAcceptable=false")
	}
}

// ============================================================================
// Test 3: Incomplete mitigations block export
// ============================================================================

func TestCEWorkflow_IncompleteMitigationsBlockExport(t *testing.T) {
	ctx := buildFullValidContext()

	// Override: set mitigations to planned status (not yet verified)
	for i := range ctx.Mitigations {
		ctx.Mitigations[i].Status = MitigationStatusPlanned
	}

	checker := NewCompletenessChecker()
	result := checker.Check(ctx)

	if result.CanExport {
		t.Error("CanExport should be false when mitigations are in planned status")
	}

	// Verify G23 (mitigations verified) specifically fails
	g23, found := findGate(result, "G23")
	if !found {
		t.Fatal("G23 gate not found in results")
	}
	if g23.Passed {
		t.Error("G23 should fail when mitigations are still in planned status")
	}
}

// ============================================================================
// Test 4: Mitigation hierarchy warning (information-only still allows export)
// ============================================================================

func TestCEWorkflow_MitigationHierarchyWarning(t *testing.T) {
	ctx := buildFullValidContext()

	// Override: set all mitigations to information type only (no design or protective)
	for i := range ctx.Mitigations {
		ctx.Mitigations[i].ReductionType = ReductionTypeInformation
		ctx.Mitigations[i].Status = MitigationStatusVerified
	}

	checker := NewCompletenessChecker()
	result := checker.Check(ctx)

	// Information-only mitigations are advisory; no gate blocks this scenario.
	// The project should still be exportable.
	if !result.CanExport {
		t.Error("CanExport should be true even with information-only mitigations (advisory, not gated)")
		for _, g := range result.Gates {
			if g.Required && !g.Passed {
				t.Errorf("  Required gate %s (%s) failed: %s", g.ID, g.Label, g.Details)
			}
		}
	}

	// Verify all required gates still pass
	if result.PassedRequired != result.TotalRequired {
		t.Errorf("PassedRequired = %d, TotalRequired = %d; want all required gates to pass with information-only mitigations",
			result.PassedRequired, result.TotalRequired)
	}
}

// ============================================================================
// Test 5: AI components require extra tech file sections
// ============================================================================

func TestCEWorkflow_AIComponentsExtraSections(t *testing.T) {
	checker := NewCompletenessChecker()

	t.Run("AI without AI tech file sections fails G42", func(t *testing.T) {
		ctx := buildFullValidContext()
		ctx.HasAI = true
		// Add AI Act classification (needed for G06 to pass with HasAI=true)
		for i := range ctx.Classifications {
			if ctx.Classifications[i].Regulation == RegulationAIAct {
				ctx.Classifications[i].ClassificationResult = "High Risk"
				ctx.Classifications[i].RiskLevel = RiskLevelHigh
			}
		}
		// TechFileSections has risk_assessment_report and hazard_log_combined but no AI sections

		result := checker.Check(ctx)

		g42, found := findGate(result, "G42")
		if !found {
			t.Fatal("G42 gate not found in results")
		}
		if g42.Passed {
			t.Error("G42 should fail when HasAI=true but AI tech file sections are missing")
		}
		if result.CanExport {
			t.Error("CanExport should be false when G42 fails")
		}
	})

	t.Run("AI with AI tech file sections passes G42", func(t *testing.T) {
		ctx := buildFullValidContext()
		ctx.HasAI = true
		// Add AI Act classification
		for i := range ctx.Classifications {
			if ctx.Classifications[i].Regulation == RegulationAIAct {
				ctx.Classifications[i].ClassificationResult = "High Risk"
				ctx.Classifications[i].RiskLevel = RiskLevelHigh
			}
		}
		// Add the required AI tech file sections
		now := time.Now()
		ctx.TechFileSections = append(ctx.TechFileSections,
			TechFileSection{
				ID:          uuid.New(),
				ProjectID:   ctx.Project.ID,
				SectionType: "ai_intended_purpose",
				Title:       "KI-Zweckbestimmung",
				Content:     "Bestimmungsgemaesse Verwendung des KI-Systems...",
				Version:     1,
				Status:      TechFileSectionStatusApproved,
				CreatedAt:   now,
				UpdatedAt:   now,
			},
			TechFileSection{
				ID:          uuid.New(),
				ProjectID:   ctx.Project.ID,
				SectionType: "ai_model_description",
				Title:       "KI-Modellbeschreibung",
				Content:     "Beschreibung des verwendeten KI-Modells...",
				Version:     1,
				Status:      TechFileSectionStatusApproved,
				CreatedAt:   now,
				UpdatedAt:   now,
			},
		)

		result := checker.Check(ctx)

		g42, found := findGate(result, "G42")
		if !found {
			t.Fatal("G42 gate not found in results")
		}
		if !g42.Passed {
			t.Errorf("G42 should pass when HasAI=true and both AI tech file sections are present; details: %s", g42.Details)
		}
		if !result.CanExport {
			t.Error("CanExport should be true when all gates pass including G42 with AI sections")
			for _, g := range result.Gates {
				if g.Required && !g.Passed {
					t.Errorf("  Required gate %s (%s) failed: %s", g.ID, g.Label, g.Details)
				}
			}
		}
	})
}

// ============================================================================
// Test 6: Export with empty/minimal project data
// ============================================================================

func TestCEWorkflow_ExportEmptyProject(t *testing.T) {
	exporter := NewDocumentExporter()

	minimalProject := &Project{
		ID:           uuid.New(),
		TenantID:     uuid.New(),
		MachineName:  "Leeres Testprojekt",
		MachineType:  "test",
		Manufacturer: "TestCorp",
		Status:       ProjectStatusDraft,
		CreatedAt:    time.Now(),
		UpdatedAt:    time.Now(),
	}

	t.Run("PDF export with empty project succeeds", func(t *testing.T) {
		data, err := exporter.ExportPDF(minimalProject, nil, nil, nil, nil, nil)
		if err != nil {
			t.Fatalf("ExportPDF returned error for empty project: %v", err)
		}
		if len(data) == 0 {
			t.Fatal("ExportPDF returned empty bytes for empty project")
		}
		if !bytes.HasPrefix(data, []byte("%PDF-")) {
			t.Errorf("PDF output does not start with %%PDF-, got first 10 bytes: %q", data[:min(10, len(data))])
		}
	})

	t.Run("Excel export with empty project succeeds", func(t *testing.T) {
		data, err := exporter.ExportExcel(minimalProject, nil, nil, nil, nil)
		if err != nil {
			t.Fatalf("ExportExcel returned error for empty project: %v", err)
		}
		if len(data) == 0 {
			t.Fatal("ExportExcel returned empty bytes for empty project")
		}
		if !bytes.HasPrefix(data, []byte("PK")) {
			t.Errorf("Excel output does not start with PK (zip signature), got first 4 bytes: %x", data[:min(4, len(data))])
		}
	})

	t.Run("Markdown export with empty project succeeds", func(t *testing.T) {
		data, err := exporter.ExportMarkdown(minimalProject, nil)
		if err != nil {
			t.Fatalf("ExportMarkdown returned error for empty project: %v", err)
		}
		if len(data) == 0 {
			t.Fatal("ExportMarkdown returned empty bytes for empty project")
		}
		mdContent := string(data)
		if !strings.Contains(mdContent, minimalProject.MachineName) {
			t.Errorf("Markdown output missing project name %q", minimalProject.MachineName)
		}
		if !strings.Contains(mdContent, "#") {
			t.Error("Markdown output missing header markers")
		}
	})

	t.Run("DOCX export with empty project succeeds", func(t *testing.T) {
		data, err := exporter.ExportDOCX(minimalProject, nil)
		if err != nil {
			t.Fatalf("ExportDOCX returned error for empty project: %v", err)
		}
		if len(data) == 0 {
			t.Fatal("ExportDOCX returned empty bytes for empty project")
		}
		if !bytes.HasPrefix(data, []byte("PK")) {
			t.Errorf("DOCX output does not start with PK (zip signature), got first 4 bytes: %x", data[:min(4, len(data))])
		}
	})
}
