package iace

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
)

const opStatesPrefix = "op_states:"

// EncodeOpStates encodes operational states into the Function field.
func EncodeOpStates(states []string) string {
	if len(states) == 0 {
		return ""
	}
	return opStatesPrefix + strings.Join(states, ",")
}

// decodeOpStates parses operational states from the Function field
// and clears the encoded value so it's not exposed as "function" text.
func decodeOpStates(h *Hazard) {
	if strings.HasPrefix(h.Function, opStatesPrefix) {
		h.OperationalStates = strings.Split(h.Function[len(opStatesPrefix):], ",")
		h.Function = ""
	}
}

// ============================================================================
// Hazard CRUD Operations
// ============================================================================

// CreateHazard creates a new hazard within a project
func (s *Store) CreateHazard(ctx context.Context, req CreateHazardRequest) (*Hazard, error) {
	h := &Hazard{
		ID:              uuid.New(),
		ProjectID:       req.ProjectID,
		ComponentID:     req.ComponentID,
		LibraryHazardID: req.LibraryHazardID,
		Name:            req.Name,
		Description:     req.Description,
		Scenario:        req.Scenario,
		Category:        req.Category,
		SubCategory:     req.SubCategory,
		Status:          HazardStatusIdentified,
		MachineModule:   req.MachineModule,
		Function:        req.Function,
		LifecyclePhase:  req.LifecyclePhase,
		HazardousZone:   req.HazardousZone,
		TriggerEvent:    req.TriggerEvent,
		AffectedPerson:  req.AffectedPerson,
		PossibleHarm:    req.PossibleHarm,
		ReviewStatus:    ReviewStatusDraft,
		CreatedAt:       time.Now().UTC(),
		UpdatedAt:       time.Now().UTC(),
	}

	_, err := s.pool.Exec(ctx, `
		INSERT INTO iace_hazards (
			id, project_id, component_id, library_hazard_id,
			name, description, scenario, category, sub_category, status,
			machine_module, function, lifecycle_phase, hazardous_zone,
			trigger_event, affected_person, possible_harm, review_status,
			created_at, updated_at
		) VALUES (
			$1, $2, $3, $4,
			$5, $6, $7, $8, $9, $10,
			$11, $12, $13, $14,
			$15, $16, $17, $18,
			$19, $20
		)
	`,
		h.ID, h.ProjectID, h.ComponentID, h.LibraryHazardID,
		h.Name, h.Description, h.Scenario, h.Category, h.SubCategory, string(h.Status),
		h.MachineModule, h.Function, h.LifecyclePhase, h.HazardousZone,
		h.TriggerEvent, h.AffectedPerson, h.PossibleHarm, string(h.ReviewStatus),
		h.CreatedAt, h.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("create hazard: %w", err)
	}

	return h, nil
}

// GetHazard retrieves a hazard by ID
func (s *Store) GetHazard(ctx context.Context, id uuid.UUID) (*Hazard, error) {
	var h Hazard
	var status, reviewStatus string

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, project_id, component_id, library_hazard_id,
			name, description, scenario, category, sub_category, status,
			machine_module, function, lifecycle_phase, hazardous_zone,
			trigger_event, affected_person, possible_harm, review_status,
			created_at, updated_at
		FROM iace_hazards WHERE id = $1
	`, id).Scan(
		&h.ID, &h.ProjectID, &h.ComponentID, &h.LibraryHazardID,
		&h.Name, &h.Description, &h.Scenario, &h.Category, &h.SubCategory, &status,
		&h.MachineModule, &h.Function, &h.LifecyclePhase, &h.HazardousZone,
		&h.TriggerEvent, &h.AffectedPerson, &h.PossibleHarm, &reviewStatus,
		&h.CreatedAt, &h.UpdatedAt,
	)
	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get hazard: %w", err)
	}

	h.Status = HazardStatus(status)
	h.ReviewStatus = ReviewStatus(reviewStatus)
	h.HazardType = DeriveHazardType(&h)
	decodeOpStates(&h)
	return &h, nil
}

// ListHazards lists all hazards for a project
func (s *Store) ListHazards(ctx context.Context, projectID uuid.UUID) ([]Hazard, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, project_id, component_id, library_hazard_id,
			name, description, scenario, category, sub_category, status,
			machine_module, function, lifecycle_phase, hazardous_zone,
			trigger_event, affected_person, possible_harm, review_status,
			created_at, updated_at
		FROM iace_hazards WHERE project_id = $1
		ORDER BY created_at ASC
	`, projectID)
	if err != nil {
		return nil, fmt.Errorf("list hazards: %w", err)
	}
	defer rows.Close()

	var hazards []Hazard
	for rows.Next() {
		var h Hazard
		var status, reviewStatus string

		err := rows.Scan(
			&h.ID, &h.ProjectID, &h.ComponentID, &h.LibraryHazardID,
			&h.Name, &h.Description, &h.Scenario, &h.Category, &h.SubCategory, &status,
			&h.MachineModule, &h.Function, &h.LifecyclePhase, &h.HazardousZone,
			&h.TriggerEvent, &h.AffectedPerson, &h.PossibleHarm, &reviewStatus,
			&h.CreatedAt, &h.UpdatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("list hazards scan: %w", err)
		}

		h.Status = HazardStatus(status)
		h.ReviewStatus = ReviewStatus(reviewStatus)
		h.HazardType = DeriveHazardType(&h)
		decodeOpStates(&h)
		hazards = append(hazards, h)
	}

	SortHazardsByISO12100(hazards)
	return hazards, nil
}

// UpdateHazard updates a hazard with a dynamic set of fields
func (s *Store) UpdateHazard(ctx context.Context, id uuid.UUID, updates map[string]interface{}) (*Hazard, error) {
	if len(updates) == 0 {
		return s.GetHazard(ctx, id)
	}

	query := "UPDATE iace_hazards SET updated_at = NOW()"
	args := []interface{}{id}
	argIdx := 2

	allowedFields := map[string]bool{
		"name": true, "description": true, "scenario": true, "category": true,
		"sub_category": true, "status": true, "component_id": true,
		"machine_module": true, "function": true, "lifecycle_phase": true,
		"hazardous_zone": true, "trigger_event": true, "affected_person": true,
		"possible_harm": true, "review_status": true,
	}

	for key, val := range updates {
		if allowedFields[key] {
			query += fmt.Sprintf(", %s = $%d", key, argIdx)
			args = append(args, val)
			argIdx++
		}
	}

	query += " WHERE id = $1"

	_, err := s.pool.Exec(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("update hazard: %w", err)
	}

	return s.GetHazard(ctx, id)
}

// DeleteHazard removes a hazard and its risk assessments.
func (s *Store) DeleteHazard(ctx context.Context, id uuid.UUID) error {
	_, err := s.pool.Exec(ctx, `DELETE FROM iace_risk_assessments WHERE hazard_id = $1`, id)
	if err != nil {
		return fmt.Errorf("delete hazard assessments: %w", err)
	}
	_, err = s.pool.Exec(ctx, `DELETE FROM iace_hazards WHERE id = $1`, id)
	if err != nil {
		return fmt.Errorf("delete hazard: %w", err)
	}
	return nil
}

// ============================================================================
// Risk Assessment Operations
// ============================================================================

// CreateRiskAssessment creates a new risk assessment for a hazard
func (s *Store) CreateRiskAssessment(ctx context.Context, assessment *RiskAssessment) error {
	if assessment.ID == uuid.Nil {
		assessment.ID = uuid.New()
	}
	if assessment.CreatedAt.IsZero() {
		assessment.CreatedAt = time.Now().UTC()
	}

	_, err := s.pool.Exec(ctx, `
		INSERT INTO iace_risk_assessments (
			id, hazard_id, version, assessment_type,
			severity, exposure, probability,
			inherent_risk, control_maturity, control_coverage,
			test_evidence_strength, c_eff, residual_risk,
			risk_level, is_acceptable, acceptance_justification,
			assessed_by, created_at
		) VALUES (
			$1, $2, $3, $4,
			$5, $6, $7,
			$8, $9, $10,
			$11, $12, $13,
			$14, $15, $16,
			$17, $18
		)
	`,
		assessment.ID, assessment.HazardID, assessment.Version, string(assessment.AssessmentType),
		assessment.Severity, assessment.Exposure, assessment.Probability,
		assessment.InherentRisk, assessment.ControlMaturity, assessment.ControlCoverage,
		assessment.TestEvidenceStrength, assessment.CEff, assessment.ResidualRisk,
		string(assessment.RiskLevel), assessment.IsAcceptable, assessment.AcceptanceJustification,
		assessment.AssessedBy, assessment.CreatedAt,
	)
	if err != nil {
		return fmt.Errorf("create risk assessment: %w", err)
	}

	return nil
}

// GetLatestAssessment retrieves the most recent risk assessment for a hazard
func (s *Store) GetLatestAssessment(ctx context.Context, hazardID uuid.UUID) (*RiskAssessment, error) {
	var a RiskAssessment
	var assessmentType, riskLevel string

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, hazard_id, version, assessment_type,
			severity, exposure, probability,
			inherent_risk, control_maturity, control_coverage,
			test_evidence_strength, c_eff, residual_risk,
			risk_level, is_acceptable, acceptance_justification,
			assessed_by, created_at
		FROM iace_risk_assessments
		WHERE hazard_id = $1
		ORDER BY version DESC, created_at DESC
		LIMIT 1
	`, hazardID).Scan(
		&a.ID, &a.HazardID, &a.Version, &assessmentType,
		&a.Severity, &a.Exposure, &a.Probability,
		&a.InherentRisk, &a.ControlMaturity, &a.ControlCoverage,
		&a.TestEvidenceStrength, &a.CEff, &a.ResidualRisk,
		&riskLevel, &a.IsAcceptable, &a.AcceptanceJustification,
		&a.AssessedBy, &a.CreatedAt,
	)
	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get latest assessment: %w", err)
	}

	a.AssessmentType = AssessmentType(assessmentType)
	a.RiskLevel = RiskLevel(riskLevel)

	return &a, nil
}

// ListAssessments lists all risk assessments for a hazard, newest first
func (s *Store) ListAssessments(ctx context.Context, hazardID uuid.UUID) ([]RiskAssessment, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, hazard_id, version, assessment_type,
			severity, exposure, probability,
			inherent_risk, control_maturity, control_coverage,
			test_evidence_strength, c_eff, residual_risk,
			risk_level, is_acceptable, acceptance_justification,
			assessed_by, created_at
		FROM iace_risk_assessments
		WHERE hazard_id = $1
		ORDER BY version DESC, created_at DESC
	`, hazardID)
	if err != nil {
		return nil, fmt.Errorf("list assessments: %w", err)
	}
	defer rows.Close()

	var assessments []RiskAssessment
	for rows.Next() {
		var a RiskAssessment
		var assessmentType, riskLevel string

		err := rows.Scan(
			&a.ID, &a.HazardID, &a.Version, &assessmentType,
			&a.Severity, &a.Exposure, &a.Probability,
			&a.InherentRisk, &a.ControlMaturity, &a.ControlCoverage,
			&a.TestEvidenceStrength, &a.CEff, &a.ResidualRisk,
			&riskLevel, &a.IsAcceptable, &a.AcceptanceJustification,
			&a.AssessedBy, &a.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("list assessments scan: %w", err)
		}

		a.AssessmentType = AssessmentType(assessmentType)
		a.RiskLevel = RiskLevel(riskLevel)

		assessments = append(assessments, a)
	}

	return assessments, nil
}

// GetLatestAssessmentsByProject fetches the latest risk assessment for ALL hazards
// of a project in a single query. Returns map[hazardID]RiskAssessment.
func (s *Store) GetLatestAssessmentsByProject(ctx context.Context, projectID uuid.UUID) (map[uuid.UUID]RiskAssessment, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT DISTINCT ON (ra.hazard_id)
			ra.id, ra.hazard_id, ra.version, ra.assessment_type,
			ra.severity, ra.exposure, ra.probability,
			ra.inherent_risk, ra.control_maturity, ra.control_coverage,
			ra.test_evidence_strength, ra.c_eff, ra.residual_risk,
			ra.risk_level, ra.is_acceptable, ra.acceptance_justification,
			ra.assessed_by, ra.created_at
		FROM iace_risk_assessments ra
		JOIN iace_hazards h ON h.id = ra.hazard_id
		WHERE h.project_id = $1
		ORDER BY ra.hazard_id, ra.version DESC, ra.created_at DESC
	`, projectID)
	if err != nil {
		return nil, fmt.Errorf("get latest assessments by project: %w", err)
	}
	defer rows.Close()

	result := make(map[uuid.UUID]RiskAssessment)
	for rows.Next() {
		var a RiskAssessment
		var assessmentType, riskLevel string
		err := rows.Scan(
			&a.ID, &a.HazardID, &a.Version, &assessmentType,
			&a.Severity, &a.Exposure, &a.Probability,
			&a.InherentRisk, &a.ControlMaturity, &a.ControlCoverage,
			&a.TestEvidenceStrength, &a.CEff, &a.ResidualRisk,
			&riskLevel, &a.IsAcceptable, &a.AcceptanceJustification,
			&a.AssessedBy, &a.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("scan assessment: %w", err)
		}
		a.AssessmentType = AssessmentType(assessmentType)
		a.RiskLevel = RiskLevel(riskLevel)
		result[a.HazardID] = a
	}
	return result, nil
}

// ============================================================================
// Risk Summary (Aggregated View)
// ============================================================================

// GetRiskSummary computes an aggregated risk overview for a project
func (s *Store) GetRiskSummary(ctx context.Context, projectID uuid.UUID) (*RiskSummaryResponse, error) {
	// Get all hazards for the project
	hazards, err := s.ListHazards(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("get risk summary - list hazards: %w", err)
	}

	summary := &RiskSummaryResponse{
		TotalHazards:  len(hazards),
		AllAcceptable: true,
	}

	if len(hazards) == 0 {
		summary.OverallRiskLevel = RiskLevelNegligible
		return summary, nil
	}

	// Batch: get all latest assessments in one query
	assessmentMap, err := s.GetLatestAssessmentsByProject(ctx, projectID)
	if err != nil {
		return nil, fmt.Errorf("get risk summary - batch assessments: %w", err)
	}

	highestRisk := RiskLevelNegligible

	for _, h := range hazards {
		ra, ok := assessmentMap[h.ID]
		var latest *RiskAssessment
		if ok {
			latest = &ra
		}
		if latest == nil {
			// Hazard without assessment counts as unassessed; consider it not acceptable
			summary.AllAcceptable = false
			continue
		}

		switch latest.RiskLevel {
		case RiskLevelNotAcceptable:
			summary.NotAcceptable++
		case RiskLevelVeryHigh:
			summary.VeryHigh++
		case RiskLevelCritical:
			summary.Critical++
		case RiskLevelHigh:
			summary.High++
		case RiskLevelMedium:
			summary.Medium++
		case RiskLevelLow:
			summary.Low++
		case RiskLevelNegligible:
			summary.Negligible++
		}

		if !latest.IsAcceptable {
			summary.AllAcceptable = false
		}

		// Track highest risk level
		if riskLevelSeverity(latest.RiskLevel) > riskLevelSeverity(highestRisk) {
			highestRisk = latest.RiskLevel
		}
	}

	summary.OverallRiskLevel = highestRisk

	return summary, nil
}

// riskLevelSeverity returns a numeric severity for risk level comparison
func riskLevelSeverity(rl RiskLevel) int {
	switch rl {
	case RiskLevelNotAcceptable:
		return 7
	case RiskLevelVeryHigh:
		return 6
	case RiskLevelCritical:
		return 5
	case RiskLevelHigh:
		return 4
	case RiskLevelMedium:
		return 3
	case RiskLevelLow:
		return 2
	case RiskLevelNegligible:
		return 1
	default:
		return 0
	}
}

