package infra

import (
	"database/sql"
	"fmt"
	"strings"

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

// ServiceSpecAudit reports drift between an app tagged `service` and the
// `service:` frontmatter block populated by the indexer (issue 0105).
type ServiceSpecAudit struct {
	AppID         string   `json:"app_id"`
	Name          string   `json:"name"`
	HasBlock      bool     `json:"has_block"`
	Runtime       string   `json:"runtime"`
	Port          int      `json:"port"`
	HealthPath    string   `json:"health_endpoint"`
	SystemdUnit   string   `json:"systemd_unit"`
	PCTargets     []string `json:"pc_targets"`
	IsLocalOnly   bool     `json:"is_local_only"`
	RestartPolicy string   `json:"restart_policy"`
	Issues        []string `json:"issues"`
	OK            bool     `json:"ok"`
}

// AuditServicesSpec lists every app with tag `service` and reports whether its
// `service:` frontmatter is complete enough for downstream monitoring
// (services_monitor app, issue 0106).
//
// Rules:
//   - block must exist (otherwise IsLocalOnly/runtime are all defaults).
//   - runtime is required (one of: systemd-user, systemd-system, docker-compose, stdio, manual).
//   - pc_targets must declare >= 1 pc_id.
//   - if runtime starts with `systemd-`, systemd_unit is required.
//   - if runtime in {systemd-*, docker-compose} and port > 0, health_endpoint is recommended (warning, not failure).
func AuditServicesSpec(registryRoot string) ([]ServiceSpecAudit, error) {
	dbPath := registryRoot + "/registry.db"
	db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL&mode=ro")
	if err != nil {
		return nil, fmt.Errorf("audit_services_spec: open db: %w", err)
	}
	defer db.Close()

	rows, err := db.Query(`
		SELECT id, name,
		       COALESCE(service_runtime,''),
		       COALESCE(service_port,0),
		       COALESCE(service_health_endpoint,''),
		       COALESCE(service_systemd_unit,''),
		       COALESCE(service_restart_policy,''),
		       COALESCE(service_is_local_only,0)
		FROM apps
		WHERE tags LIKE '%service%'
		ORDER BY id
	`)
	if err != nil {
		return nil, fmt.Errorf("audit_services_spec: query: %w", err)
	}
	defer rows.Close()

	var out []ServiceSpecAudit
	for rows.Next() {
		var a ServiceSpecAudit
		var localOnly int
		if err := rows.Scan(
			&a.AppID, &a.Name,
			&a.Runtime, &a.Port, &a.HealthPath, &a.SystemdUnit, &a.RestartPolicy, &localOnly,
		); err != nil {
			return nil, fmt.Errorf("audit_services_spec: scan: %w", err)
		}
		a.IsLocalOnly = localOnly != 0
		a.HasBlock = a.Runtime != "" || a.SystemdUnit != "" || a.Port != 0 || a.HealthPath != ""

		// pc_targets from service_targets table.
		tRows, err := db.Query(
			"SELECT pc_id FROM service_targets WHERE app_id = ? ORDER BY pc_id",
			a.AppID,
		)
		if err != nil {
			return nil, fmt.Errorf("audit_services_spec: service_targets query: %w", err)
		}
		for tRows.Next() {
			var pc string
			if err := tRows.Scan(&pc); err != nil {
				tRows.Close()
				return nil, err
			}
			a.PCTargets = append(a.PCTargets, pc)
		}
		tRows.Close()

		// Validate.
		if !a.HasBlock {
			a.Issues = append(a.Issues, "missing service: block in app.md")
		}
		if a.Runtime == "" {
			a.Issues = append(a.Issues, "missing service.runtime")
		} else if !validRuntimes[a.Runtime] {
			a.Issues = append(a.Issues, "invalid service.runtime: "+a.Runtime)
		}
		if len(a.PCTargets) == 0 {
			a.Issues = append(a.Issues, "missing service.pc_targets (>= 1 required)")
		}
		if strings.HasPrefix(a.Runtime, "systemd-") && a.SystemdUnit == "" {
			a.Issues = append(a.Issues, "runtime systemd-* requires service.systemd_unit")
		}
		if a.RestartPolicy != "" && !validRestart[a.RestartPolicy] {
			a.Issues = append(a.Issues, "invalid service.restart_policy: "+a.RestartPolicy)
		}

		a.OK = len(a.Issues) == 0
		out = append(out, a)
	}
	return out, rows.Err()
}

var validRuntimes = map[string]bool{
	"systemd-user":   true,
	"systemd-system": true,
	"docker-compose": true,
	"stdio":          true,
	"manual":         true,
}

var validRestart = map[string]bool{
	"always":     true,
	"on-failure": true,
	"none":       true,
}
