package infra

import (
	"crypto/rand"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"time"

	_ "github.com/mattn/go-sqlite3"
)

// ProposalFromFailure creates a proposal row in registry.db for each failed
// CheckResult. It opens the database at registryDB, filters results with
// Status=="fail", and inserts one proposal per failure using:
//   - kind="new_function" for severity=="critical" checks (highest urgency proxy)
//   - kind="improve_function" for severity=="warning" checks
//
// Note: the proposals table kind constraint only allows
// (new_function, new_type, improve_function, improve_type, new_pipeline).
// Until a dedicated "bug" kind is added, we use new_function/improve_function
// as the closest proxies for critical and warning failures respectively.
//
// Returns the list of proposal IDs created, or an error if the DB cannot be
// opened or any INSERT fails.
func ProposalFromFailure(registryDB string, appID string, results []CheckResult, executionID string) ([]string, error) {
	db, err := SQLiteOpen(registryDB, "")
	if err != nil {
		return nil, fmt.Errorf("proposal_from_failure: open registry db: %w", err)
	}
	defer db.Close()

	var created []string
	now := time.Now().UTC().Format(time.RFC3339)

	for _, r := range results {
		if r.Status != "fail" {
			continue
		}

		propID, err := generatePropID()
		if err != nil {
			return created, fmt.Errorf("proposal_from_failure: generate id: %w", err)
		}

		kind := proposalKind(r.Severity)
		title := fmt.Sprintf("e2e fail: %s::%s", appID, r.ID)
		desc := buildDescription(r)

		evidence, _ := json.Marshal(map[string]any{
			"check_id":     r.ID,
			"execution_id": executionID,
			"exit_code":    r.ExitCode,
			"error":        r.Error,
			"severity":     r.Severity,
		})

		_, err = db.Exec(`
			INSERT INTO proposals (id, kind, target_id, title, description, evidence, status, created_by, reviewed_by, created_at, updated_at)
			VALUES (?, ?, ?, ?, ?, ?, 'pending', 'reactive_loop', '', ?, ?)`,
			propID, kind, appID, title, desc, string(evidence), now, now,
		)
		if err != nil {
			return created, fmt.Errorf("proposal_from_failure: insert proposal %s: %w", propID, err)
		}
		created = append(created, propID)
	}

	return created, nil
}

// proposalKind maps check severity to an allowed proposals.kind value.
// critical -> new_function (highest urgency proxy)
// warning  -> improve_function
func proposalKind(severity string) string {
	if severity == "warning" {
		return "improve_function"
	}
	return "new_function"
}

// buildDescription assembles a human-readable description for the proposal.
func buildDescription(r CheckResult) string {
	desc := fmt.Sprintf("E2E check %q failed (severity: %s, exit_code: %d).", r.ID, r.Severity, r.ExitCode)
	if r.Error != "" {
		desc += "\n\nError: " + r.Error
	}
	if r.Stdout != "" {
		desc += "\n\nStdout:\n" + r.Stdout
	}
	if r.Stderr != "" {
		desc += "\n\nStderr:\n" + r.Stderr
	}
	desc += "\n\nSugerencia: revisar el comando/endpoint del check y el estado del servicio."
	return desc
}

// generatePropID generates a random proposal ID of the form "prop_<16hexchars>".
func generatePropID() (string, error) {
	b := make([]byte, 8)
	if _, err := rand.Read(b); err != nil {
		return "", fmt.Errorf("crypto/rand: %w", err)
	}
	return "prop_" + hex.EncodeToString(b), nil
}
