// Package policy implements Oikos classification and policy evaluation.
// Determines risk class, autonomy route, and approval requirements.
package policy

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

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
)

// ClassificationResult holds the outcome of classifying a signal.
type ClassificationResult struct {
	RiskClass       string
	Route           string // 'auto-act', 'escalate', 'hold'
	RecommendedAction json.RawMessage
	AutonomyCheck   string
	BlastRadius     []uuid.UUID
	CorrelationID   string
	Reasoning       json.RawMessage
}

// Classify evaluates a signal against policy rules to determine the action route.
// ctx must have a DB connection pool accessible via a helper interface.
type Classifier struct {
	DB interface {
		QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
		Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
		Exec(ctx context.Context, sql string, args ...any) (int64, error)
	}
}

// NewClassifier creates a classifier with a DB query interface.
func NewClassifier(dbc interface {
	QueryRow(ctx context.Context, sql string, args ...any) pgx.Row
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
	Exec(ctx context.Context, sql string, args ...any) (int64, error)
}) *Classifier {
	return &Classifier{DB: dbc}
}

// ClassifySignal evaluates a signal and returns the classification result.
func (c *Classifier) ClassifySignal(ctx context.Context, signalEntityID, targetEntityID uuid.UUID,
	kind, severity, correlationID string) (*ClassificationResult, error) {

	// Determine target entity type
	var entityType string
	err := c.DB.QueryRow(ctx,
		"SELECT type FROM entities WHERE id = $1", targetEntityID).Scan(&entityType)
	if err != nil {
		return nil, fmt.Errorf("%w: target entity %s", domain.ErrNotFound, targetEntityID)
	}

	// Look up risk class for this entity type and action
	var riskClass string
	var approvalRequired string
	err = c.DB.QueryRow(ctx, `
		SELECT rc.name, rc.approval_required
		FROM risk_classes rc
		WHERE rc.name = (
			SELECT COALESCE(ar.risk_class, 'reversible_low')
			FROM approval_rules ar
			WHERE ar.entity_type = $1 AND ar.action = $2
			LIMIT 1
		)`, entityType, kind).Scan(&riskClass, &approvalRequired)
	if err != nil {
		// Default to escalate
		riskClass = "reversible_low"
		approvalRequired = "operator"
	}

	// Check global autonomy setting
	var globalAutoAct string
	err = c.DB.QueryRow(ctx,
		"SELECT value FROM autonomy_settings WHERE key = 'global.auto_act'").Scan(&globalAutoAct)
	if err != nil {
		globalAutoAct = "on" // default to on
	}

	// Check per-entity kill-switch
	var slug string
	c.DB.QueryRow(ctx, "SELECT slug FROM entities WHERE id = $1", targetEntityID).Scan(&slug)

	var entityAutoAct string
	if slug != "" {
		c.DB.QueryRow(ctx,
			"SELECT value FROM autonomy_settings WHERE key = $1",
			"never_auto_act."+slug).Scan(&entityAutoAct)
	}

	// Determine route
	route, autonomyCheck := determineRoute(globalAutoAct, entityAutoAct, approvalRequired)

	// Compute blast radius
	blastRadius := computeBlastRadius(ctx, c.DB, targetEntityID)

	reasoning := map[string]any{
		"entity_type":     entityType,
		"risk_class":      riskClass,
		"approval_rule":   approvalRequired,
		"global_auto_act": globalAutoAct,
		"entity_slug":     slug,
	}

	reasoningJSON, _ := json.Marshal(reasoning)
	recommended, _ := json.Marshal(map[string]any{
		"action": kind,
		"reason": fmt.Sprintf("signal %s on %s", severity, entityType),
	})

	return &ClassificationResult{
		RiskClass:        riskClass,
		Route:            route,
		RecommendedAction: recommended,
		AutonomyCheck:    autonomyCheck,
		BlastRadius:      blastRadius,
		CorrelationID:    correlationID,
		Reasoning:        reasoningJSON,
	}, nil
}

// determineRoute evaluates autonomy settings and approval requirements to
// decide whether a signal should auto-act, escalate, or hold for approval.
func determineRoute(globalAutoAct, entityAutoAct, approvalRequired string) (route, autonomyCheck string) {
	if globalAutoAct == "off" || globalAutoAct == "false" {
		return "escalate", "blocked: global auto_act disabled"
	}
	if entityAutoAct == "true" {
		return "escalate", "blocked: per-entity kill-switch"
	}
	if approvalRequired == "none" {
		return "auto-act", "allowed"
	}
	return "escalate", "requires approval: " + approvalRequired
}

// computeBlastRadius traverses relationships to find affected entities.
func computeBlastRadius(ctx context.Context, dbc interface {
	Query(ctx context.Context, sql string, args ...any) (pgx.Rows, error)
}, entityID uuid.UUID) []uuid.UUID {
	rows, err := dbc.Query(ctx, `
		SELECT entity_id FROM blast_radius($1, 3)`, entityID)
	if err != nil {
		return nil
	}
	defer rows.Close()

	var ids []uuid.UUID
	for rows.Next() {
		var id uuid.UUID
		if err := rows.Scan(&id); err == nil {
			ids = append(ids, id)
		}
	}
	return ids
}