package httpapi

import (
	"context"
	"fmt"
	"strings"

	"github.com/dtoro/oikos/internal/adapters/postgres/sqlcgen"
	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/httpapi/gen"
	"github.com/dtoro/oikos/internal/observability"
	"github.com/google/uuid"
)

// ─── Approval Rules (Policy) ───────────────────────────────────────────

func (s *Server) ListApprovalRules(ctx context.Context, req gen.ListApprovalRulesRequestObject) (gen.ListApprovalRulesResponseObject, error) {
	rows, err := s.pool.Query(ctx, `
		SELECT id, entity_type, action, risk_class, autonomy_level,
		       COALESCE((SELECT slug FROM entities WHERE id = scope_entity), ''),
		       version, updated_at
		FROM approval_rules ORDER BY entity_type, action`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := []gen.ApprovalRule{}
	for rows.Next() {
		var rule gen.ApprovalRule
		var scopeSlug string
		if err := rows.Scan(&rule.Id, &rule.EntityType, &rule.Action,
			&rule.RiskClass, &rule.AutonomyLevel, &scopeSlug,
			&rule.Version); err != nil {
			return nil, err
		}
		if scopeSlug != "" {
			rule.ScopeEntity = &scopeSlug
		}
		items = append(items, rule)
	}
	if rows.Err() != nil {
		return nil, rows.Err()
	}
	if items == nil {
		items = []gen.ApprovalRule{}
	}
	return gen.ListApprovalRules200JSONResponse{Items: items}, nil
}

func (s *Server) CreateApprovalRule(ctx context.Context, req gen.CreateApprovalRuleRequestObject) (gen.CreateApprovalRuleResponseObject, error) {
	if req.Body == nil {
		return nil, fmt.Errorf("%w: request body is required", domain.ErrInvalidInput)
	}

	id, err := uuid.NewV7()
	if err != nil {
		return nil, err
	}

	var scopeEntity *uuid.UUID
	if req.Body.ScopeEntity != nil && *req.Body.ScopeEntity != "" {
		se, rerr := s.resolveEntityID(ctx, *req.Body.ScopeEntity)
		if rerr != nil {
			return nil, rerr
		}
		scopeEntity = &se
	}

	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	_, err = tx.Exec(ctx, `
		INSERT INTO approval_rules (id, entity_type, action, risk_class, autonomy_level, scope_entity)
		VALUES ($1, $2, $3, $4, $5, $6)`,
		id, req.Body.EntityType, req.Body.Action, req.Body.RiskClass,
		string(req.Body.AutonomyLevel), scopeEntity)
	if err != nil {
		if strings.Contains(err.Error(), "unique") || strings.Contains(err.Error(), "duplicate") {
			return nil, fmt.Errorf("%w: rule for %s/%s already exists", domain.ErrAlreadyExists,
				coalesceStr(req.Body.EntityType, "*"), req.Body.Action)
		}
		return nil, err
	}

	actorType, actor := actorInfo(ctx)
	if auditErr := observability.Audit(ctx, sqlcgen.New(tx), actorType, actor, "create",
		&id, "POST", "/api/v1/policy/approval-rules", "",
		nil,
		map[string]any{"action": req.Body.Action, "risk_class": req.Body.RiskClass}); auditErr != nil {
		return nil, auditErr
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	// Return 202 pending approval (dual-control).
	return gen.CreateApprovalRule202JSONResponse{}, nil
}

func (s *Server) PatchApprovalRule(ctx context.Context, req gen.PatchApprovalRuleRequestObject) (gen.PatchApprovalRuleResponseObject, error) {
	if req.Body == nil {
		return nil, fmt.Errorf("%w: request body is required", domain.ErrInvalidInput)
	}

	id, err := s.resolveEntityID(ctx, req.Id)
	if err != nil {
		return nil, err
	}

	var scopeEntity *uuid.UUID
	if req.Body.ScopeEntity != nil && *req.Body.ScopeEntity != "" {
		se, rerr := s.resolveEntityID(ctx, *req.Body.ScopeEntity)
		if rerr != nil {
			return nil, rerr
		}
		scopeEntity = &se
	}

	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	result, err := tx.Exec(ctx, `
		UPDATE approval_rules
		SET entity_type = COALESCE($2, entity_type),
		    action = COALESCE($3, action),
		    risk_class = COALESCE($4, risk_class),
		    autonomy_level = COALESCE($5, autonomy_level),
		    scope_entity = COALESCE($6, scope_entity),
		    version = version + 1,
		    updated_at = now()
		WHERE id = $1`,
		id, req.Body.EntityType, req.Body.Action, req.Body.RiskClass,
		string(req.Body.AutonomyLevel), scopeEntity)
	if err != nil {
		return nil, err
	}
	if result.RowsAffected() == 0 {
		return nil, fmt.Errorf("%w: approval rule %s", domain.ErrNotFound, req.Id)
	}

	actorType, actor := actorInfo(ctx)
	if auditErr := observability.Audit(ctx, sqlcgen.New(tx), actorType, actor, "patch",
		&id, "PATCH", "/api/v1/policy/approval-rules/"+req.Id, "",
		nil,
		map[string]any{"action": req.Body.Action}); auditErr != nil {
		return nil, auditErr
	}

	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}

	return gen.PatchApprovalRule202JSONResponse{}, nil
}
