package iace

import (
	"context"
	"encoding/json"
	"fmt"
	"time"

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

// ============================================================================
// Project CRUD Operations
// ============================================================================

// CreateProject creates a new IACE project
func (s *Store) CreateProject(ctx context.Context, tenantID uuid.UUID, req CreateProjectRequest) (*Project, error) {
	project := &Project{
		ID:              uuid.New(),
		TenantID:        tenantID,
		ParentProjectID: req.ParentProjectID,
		MachineName:     req.MachineName,
		MachineType:     req.MachineType,
		Manufacturer:    req.Manufacturer,
		CustomerName:    req.CustomerName,
		Description:     req.Description,
		NarrativeText:   req.NarrativeText,
		Status:          ProjectStatusDraft,
		CEMarkingTarget: req.CEMarkingTarget,
		Metadata:        req.Metadata,
		CreatedAt:       time.Now().UTC(),
		UpdatedAt:       time.Now().UTC(),
	}

	_, err := s.pool.Exec(ctx, `
		INSERT INTO iace_projects (
			id, tenant_id, parent_project_id, machine_name, machine_type, manufacturer,
			customer_name, description, narrative_text, status, ce_marking_target,
			completeness_score, risk_summary, triggered_regulations, metadata,
			created_at, updated_at, archived_at
		) VALUES (
			$1, $2, $3, $4, $5, $6,
			$7, $8, $9, $10, $11,
			$12, $13, $14, $15,
			$16, $17, $18
		)
	`,
		project.ID, project.TenantID, project.ParentProjectID,
		project.MachineName, project.MachineType, project.Manufacturer,
		project.CustomerName, project.Description, project.NarrativeText, string(project.Status), project.CEMarkingTarget,
		project.CompletenessScore, nil, project.TriggeredRegulations, project.Metadata,
		project.CreatedAt, project.UpdatedAt, project.ArchivedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("create project: %w", err)
	}

	return project, nil
}

// GetProject retrieves a project by ID
func (s *Store) GetProject(ctx context.Context, id uuid.UUID) (*Project, error) {
	var p Project
	var status string
	var riskSummary, triggeredRegulations, metadata []byte

	err := s.pool.QueryRow(ctx, `
		SELECT
			id, tenant_id, parent_project_id, machine_name, machine_type, manufacturer,
			COALESCE(customer_name, ''), description, narrative_text, status, ce_marking_target,
			completeness_score, risk_summary, triggered_regulations, metadata,
			created_at, updated_at, archived_at
		FROM iace_projects WHERE id = $1
	`, id).Scan(
		&p.ID, &p.TenantID, &p.ParentProjectID, &p.MachineName, &p.MachineType, &p.Manufacturer,
		&p.CustomerName, &p.Description, &p.NarrativeText, &status, &p.CEMarkingTarget,
		&p.CompletenessScore, &riskSummary, &triggeredRegulations, &metadata,
		&p.CreatedAt, &p.UpdatedAt, &p.ArchivedAt,
	)
	if err == pgx.ErrNoRows {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get project: %w", err)
	}

	p.Status = ProjectStatus(status)
	json.Unmarshal(riskSummary, &p.RiskSummary)
	json.Unmarshal(triggeredRegulations, &p.TriggeredRegulations)
	json.Unmarshal(metadata, &p.Metadata)

	return &p, nil
}

// ListProjects lists all projects for a tenant
func (s *Store) ListProjects(ctx context.Context, tenantID uuid.UUID) ([]Project, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, tenant_id, parent_project_id, machine_name, machine_type, manufacturer,
			COALESCE(customer_name, ''), description, narrative_text, status, ce_marking_target,
			completeness_score, risk_summary, triggered_regulations, metadata,
			created_at, updated_at, archived_at
		FROM iace_projects WHERE tenant_id = $1
		ORDER BY created_at DESC
	`, tenantID)
	if err != nil {
		return nil, fmt.Errorf("list projects: %w", err)
	}
	defer rows.Close()

	var projects []Project
	for rows.Next() {
		var p Project
		var status string
		var riskSummary, triggeredRegulations, metadata []byte

		err := rows.Scan(
			&p.ID, &p.TenantID, &p.ParentProjectID, &p.MachineName, &p.MachineType, &p.Manufacturer,
			&p.CustomerName, &p.Description, &p.NarrativeText, &status, &p.CEMarkingTarget,
			&p.CompletenessScore, &riskSummary, &triggeredRegulations, &metadata,
			&p.CreatedAt, &p.UpdatedAt, &p.ArchivedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("list projects scan: %w", err)
		}

		p.Status = ProjectStatus(status)
		json.Unmarshal(riskSummary, &p.RiskSummary)
		json.Unmarshal(triggeredRegulations, &p.TriggeredRegulations)
		json.Unmarshal(metadata, &p.Metadata)

		projects = append(projects, p)
	}

	return projects, nil
}

// UpdateProject updates an existing project's mutable fields
func (s *Store) UpdateProject(ctx context.Context, id uuid.UUID, req UpdateProjectRequest) (*Project, error) {
	// Fetch current project first
	project, err := s.GetProject(ctx, id)
	if err != nil {
		return nil, err
	}
	if project == nil {
		return nil, nil
	}

	// Apply partial updates
	if req.MachineName != nil {
		project.MachineName = *req.MachineName
	}
	if req.MachineType != nil {
		project.MachineType = *req.MachineType
	}
	if req.Manufacturer != nil {
		project.Manufacturer = *req.Manufacturer
	}
	if req.CustomerName != nil {
		project.CustomerName = *req.CustomerName
	}
	if req.Description != nil {
		project.Description = *req.Description
	}
	if req.NarrativeText != nil {
		project.NarrativeText = *req.NarrativeText
	}
	if req.CEMarkingTarget != nil {
		project.CEMarkingTarget = *req.CEMarkingTarget
	}
	if req.Metadata != nil {
		project.Metadata = *req.Metadata
	}

	project.UpdatedAt = time.Now().UTC()

	_, err = s.pool.Exec(ctx, `
		UPDATE iace_projects SET
			machine_name = $2, machine_type = $3, manufacturer = $4,
			customer_name = $5,
			description = $6, narrative_text = $7, ce_marking_target = $8,
			metadata = $9, updated_at = $10
		WHERE id = $1
	`,
		id, project.MachineName, project.MachineType, project.Manufacturer,
		project.CustomerName,
		project.Description, project.NarrativeText, project.CEMarkingTarget,
		project.Metadata, project.UpdatedAt,
	)
	if err != nil {
		return nil, fmt.Errorf("update project: %w", err)
	}

	return project, nil
}

// ArchiveProject sets the archived_at timestamp and status for a project
func (s *Store) ArchiveProject(ctx context.Context, id uuid.UUID) error {
	now := time.Now().UTC()
	_, err := s.pool.Exec(ctx, `
		UPDATE iace_projects SET
			status = $2, archived_at = $3, updated_at = $3
		WHERE id = $1
	`, id, string(ProjectStatusArchived), now)
	if err != nil {
		return fmt.Errorf("archive project: %w", err)
	}
	return nil
}

// UpdateProjectStatus updates the lifecycle status of a project
func (s *Store) UpdateProjectStatus(ctx context.Context, id uuid.UUID, status ProjectStatus) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE iace_projects SET status = $2, updated_at = NOW()
		WHERE id = $1
	`, id, string(status))
	if err != nil {
		return fmt.Errorf("update project status: %w", err)
	}
	return nil
}

// UpdateProjectCompleteness updates the completeness score and risk summary
func (s *Store) UpdateProjectCompleteness(ctx context.Context, id uuid.UUID, score float64, riskSummary map[string]int) error {
	riskSummaryJSON, err := json.Marshal(riskSummary)
	if err != nil {
		return fmt.Errorf("marshal risk summary: %w", err)
	}

	_, err = s.pool.Exec(ctx, `
		UPDATE iace_projects SET
			completeness_score = $2, risk_summary = $3, updated_at = NOW()
		WHERE id = $1
	`, id, score, riskSummaryJSON)
	if err != nil {
		return fmt.Errorf("update project completeness: %w", err)
	}
	return nil
}

// UpdateProjectMetadata replaces the metadata JSON for a project.
func (s *Store) UpdateProjectMetadata(ctx context.Context, id uuid.UUID, metadata json.RawMessage) error {
	_, err := s.pool.Exec(ctx, `
		UPDATE iace_projects SET metadata = $2, updated_at = NOW()
		WHERE id = $1
	`, id, metadata)
	if err != nil {
		return fmt.Errorf("update project metadata: %w", err)
	}
	return nil
}

// ListVariants returns all variant sub-projects for a given parent project
func (s *Store) ListVariants(ctx context.Context, parentID uuid.UUID) ([]Project, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT
			id, tenant_id, parent_project_id, machine_name, machine_type, manufacturer,
			COALESCE(customer_name, ''), description, narrative_text, status, ce_marking_target,
			completeness_score, risk_summary, triggered_regulations, metadata,
			created_at, updated_at, archived_at
		FROM iace_projects WHERE parent_project_id = $1
		ORDER BY created_at DESC
	`, parentID)
	if err != nil {
		return nil, fmt.Errorf("list variants: %w", err)
	}
	defer rows.Close()

	var variants []Project
	for rows.Next() {
		var p Project
		var status string
		var riskSummary, triggeredRegulations, metadata []byte

		err := rows.Scan(
			&p.ID, &p.TenantID, &p.ParentProjectID, &p.MachineName, &p.MachineType, &p.Manufacturer,
			&p.CustomerName, &p.Description, &p.NarrativeText, &status, &p.CEMarkingTarget,
			&p.CompletenessScore, &riskSummary, &triggeredRegulations, &metadata,
			&p.CreatedAt, &p.UpdatedAt, &p.ArchivedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("list variants scan: %w", err)
		}

		p.Status = ProjectStatus(status)
		json.Unmarshal(riskSummary, &p.RiskSummary)
		json.Unmarshal(triggeredRegulations, &p.TriggeredRegulations)
		json.Unmarshal(metadata, &p.Metadata)

		variants = append(variants, p)
	}

	return variants, nil
}

// GetVariantGap computes the gap analysis between a variant and its base project.
// It counts hazards and measures in each, and returns the categories affected.
func (s *Store) GetVariantGap(ctx context.Context, variantID uuid.UUID) (*VariantGap, error) {
	variant, err := s.GetProject(ctx, variantID)
	if err != nil {
		return nil, fmt.Errorf("get variant: %w", err)
	}
	if variant == nil {
		return nil, fmt.Errorf("variant project not found")
	}
	if variant.ParentProjectID == nil {
		return nil, fmt.Errorf("project is not a variant (no parent_project_id)")
	}

	parent, err := s.GetProject(ctx, *variant.ParentProjectID)
	if err != nil {
		return nil, fmt.Errorf("get parent: %w", err)
	}
	if parent == nil {
		return nil, fmt.Errorf("parent project not found")
	}

	// Count hazards and measures for both projects
	parentHazards, parentMeasures, err := s.countHazardsAndMeasures(ctx, parent.ID)
	if err != nil {
		return nil, fmt.Errorf("count parent stats: %w", err)
	}
	variantHazards, variantMeasures, err := s.countHazardsAndMeasures(ctx, variant.ID)
	if err != nil {
		return nil, fmt.Errorf("count variant stats: %w", err)
	}

	// Get unique hazard categories in the variant
	categories, err := s.getVariantHazardCategories(ctx, variant.ID)
	if err != nil {
		return nil, fmt.Errorf("get variant categories: %w", err)
	}

	return &VariantGap{
		BaseProject: ProjectSummary{
			ID:           parent.ID,
			Name:         parent.MachineName,
			HazardCount:  parentHazards,
			MeasureCount: parentMeasures,
		},
		Variant: ProjectSummary{
			ID:           variant.ID,
			Name:         variant.MachineName,
			HazardCount:  variantHazards,
			MeasureCount: variantMeasures,
		},
		Gap: GapDetail{
			AdditionalHazards:  variantHazards,
			AdditionalMeasures: variantMeasures,
			CategoriesAffected: categories,
		},
	}, nil
}

// countHazardsAndMeasures returns (hazardCount, measureCount) for a project
func (s *Store) countHazardsAndMeasures(ctx context.Context, projectID uuid.UUID) (int, int, error) {
	var hazardCount int
	err := s.pool.QueryRow(ctx,
		`SELECT COUNT(*) FROM iace_hazards WHERE project_id = $1`, projectID,
	).Scan(&hazardCount)
	if err != nil {
		return 0, 0, err
	}

	var measureCount int
	err = s.pool.QueryRow(ctx, `
		SELECT COUNT(*) FROM iace_mitigations m
		JOIN iace_hazards h ON m.hazard_id = h.id
		WHERE h.project_id = $1
	`, projectID).Scan(&measureCount)
	if err != nil {
		return 0, 0, err
	}

	return hazardCount, measureCount, nil
}

// getVariantHazardCategories returns distinct hazard categories for a project
func (s *Store) getVariantHazardCategories(ctx context.Context, projectID uuid.UUID) ([]string, error) {
	rows, err := s.pool.Query(ctx,
		`SELECT DISTINCT category FROM iace_hazards WHERE project_id = $1 ORDER BY category`,
		projectID,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var categories []string
	for rows.Next() {
		var cat string
		if err := rows.Scan(&cat); err != nil {
			return nil, err
		}
		categories = append(categories, cat)
	}
	return categories, nil
}

