package cli

import (
	"context"
	"fmt"
	"os"
	"time"

	"github.com/google/uuid"
	"github.com/spf13/cobra"

	"git.cloudinit.dev/coreci/orca/internal/certpaths"
	"git.cloudinit.dev/coreci/orca/internal/model"
	"git.cloudinit.dev/coreci/orca/internal/security"
	"git.cloudinit.dev/coreci/orca/internal/store"
)

const (
	initCAN       = "orca-internal-ca"
	localhostName = "localhost"
	localhostAddr = "localhost:8443"
)

var initCmd = &cobra.Command{
	Use:   "init",
	Short: "Initialize local orca state with full bootstrap",
	Long: `Initialize the local orca state directory and provision all
dependencies required for ` + "`orca doctor`" + ` to pass:

  1. Create the namespace directory (honors $ORCA_HOME; defaults to ~/.orca)
  2. Open and migrate the SQLite database (migrations 0001..0006)
  3. Bootstrap the internal CA (ca.crt + ca.key) if not already present
  4. Generate the server cert (server.crt + server.key) if not already present
  5. Auto-detect the local OS via /etc/os-release
  6. Register a localhost node (kind=localhost, os=<detected>)

Idempotent: re-running is safe and will refresh last_seen + os on the
localhost node without regenerating certs or changing the node ID.`,
	RunE: func(cmd *cobra.Command, args []string) error {
		return runInit(cmd.OutOrStdout())
	},
}

func runInit(out interface{ Write([]byte) (int, error) }) error {
	dir := certpaths.Dir()

	type stepResult struct {
		Label  string `json:"label"`
		Status string `json:"status"`
		Detail string `json:"detail,omitempty"`
	}
	type initSummary struct {
		Namespace       string       `json:"namespace"`
		Database        string       `json:"database"`
		CAFingerprint   string       `json:"ca_fingerprint,omitempty"`
		CertFingerprint string       `json:"cert_fingerprint,omitempty"`
		OS              string       `json:"os"`
		NodeID          string       `json:"node_id"`
		NodeName        string       `json:"node_name"`
		Steps           []stepResult `json:"steps"`
	}
	summary := initSummary{Namespace: dir}

	// Step 1: namespace dir.
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return fmt.Errorf("create orca dir: %w", err)
	}
	summary.Steps = append(summary.Steps, stepResult{Label: "namespace", Status: "ok", Detail: dir})
	if !jsonOutput {
		fmt.Fprintf(out, "✓ Namespace dir: %s\n", dir)
	}

	// Step 2: database + migrations.
	dbPath := certpaths.DBPath()
	db, err := store.Open(dbPath)
	if err != nil {
		return fmt.Errorf("open database: %w", err)
	}
	defer db.Close()
	summary.Database = dbPath
	summary.Steps = append(summary.Steps, stepResult{Label: "database", Status: "ok", Detail: dbPath})
	if !jsonOutput {
		fmt.Fprintf(out, "✓ Database initialized: %s\n", dbPath)
	}

	// Step 3: CA bootstrap (idempotent — CAInit has a fast-path).
	ca, err := security.CAInit(dir, initCAN)
	if err != nil {
		return fmt.Errorf("bootstrap CA: %w", err)
	}
	caFp := ca.Fingerprint()
	summary.CAFingerprint = caFp
	summary.Steps = append(summary.Steps, stepResult{Label: "ca", Status: "ok", Detail: caFp[:16] + "..."})
	if !jsonOutput {
		fmt.Fprintf(out, "✓ CA provisioned: fp=%s\n", caFp[:16]+"...")
	}

	// Step 4: server cert (only if absent — D-036 idempotency).
	certPath := certpaths.ServerCertPath()
	certFp := ""
	if _, err := os.Stat(certPath); err == nil {
		// Already exists — load fingerprint for the summary.
		if fp, err := security.Fingerprint(certPath); err == nil {
			certFp = fp
		}
		summary.Steps = append(summary.Steps, stepResult{Label: "server-cert", Status: "skipped", Detail: "already present"})
	} else if os.IsNotExist(err) {
		keyPEM, csrPEM, err := security.GenerateCSR("localhost", []string{"localhost", "127.0.0.1"})
		if err != nil {
			return fmt.Errorf("generate server CSR: %w", err)
		}
		certPEM, err := ca.SignCSR(csrPEM)
		if err != nil {
			return fmt.Errorf("sign server CSR: %w", err)
		}
		if err := security.WriteCert(certPath, certPEM); err != nil {
			return fmt.Errorf("write server cert: %w", err)
		}
		if err := security.WriteKey(certpaths.ServerKeyPath(), keyPEM); err != nil {
			return fmt.Errorf("write server key: %w", err)
		}
		certFp = security.FingerprintOf(parseFirstCertDER(certPEM))
		summary.Steps = append(summary.Steps, stepResult{Label: "server-cert", Status: "ok", Detail: certFp[:16] + "..."})
	} else {
		return fmt.Errorf("stat server cert: %w", err)
	}
	summary.CertFingerprint = certFp
	if !jsonOutput {
		if certFp != "" {
			fmt.Fprintf(out, "✓ Server cert provisioned: fp=%s\n", certFp[:16]+"...")
		} else {
			fmt.Fprintf(out, "✓ Server cert: already present\n")
		}
	}

	// Step 5: OS detection.
	osDetected := detectOS()
	summary.OS = osDetected
	summary.Steps = append(summary.Steps, stepResult{Label: "os", Status: "ok", Detail: osDetected})
	if !jsonOutput {
		fmt.Fprintf(out, "✓ OS detected: %s\n", osDetected)
	}

	// Step 6: localhost node upsert (idempotent per D-036).
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()

	repo := store.NewNodeRepo(db)
	existing, err := repo.GetByName(ctx, localhostName)
	if err == nil {
		// Refresh last_seen + os; keep id and joined_at.
		if err := repo.UpdateLastSeenAndOS(ctx, existing.ID, osDetected); err != nil {
			return fmt.Errorf("refresh localhost node: %w", err)
		}
		summary.NodeID = existing.ID
		summary.NodeName = existing.Name
		summary.Steps = append(summary.Steps, stepResult{Label: "localhost-node", Status: "refreshed", Detail: existing.ID})
		if !jsonOutput {
			fmt.Fprintf(out, "✓ Localhost node refreshed: %s (os=%s)\n", existing.ID, osDetected)
		}
	} else if err == store.ErrNotFound {
		node := &model.Node{
			ID:       uuid.NewString(),
			Name:     localhostName,
			Address:  localhostAddr,
			State:    model.NodeStateReady,
			JoinedAt: time.Now().UTC(),
			LastSeen: time.Now().UTC(),
			Kind:     string(model.NodeKindLocalhost),
			OS:       osDetected,
		}
		if err := repo.Insert(ctx, node); err != nil {
			return fmt.Errorf("insert localhost node: %w", err)
		}
		summary.NodeID = node.ID
		summary.NodeName = node.Name
		summary.Steps = append(summary.Steps, stepResult{Label: "localhost-node", Status: "ok", Detail: node.ID})
		if !jsonOutput {
			fmt.Fprintf(out, "✓ Localhost node registered: %s (os=%s)\n", node.ID, osDetected)
		}
	} else {
		return fmt.Errorf("lookup localhost node: %w", err)
	}

	if jsonOutput {
		return printJSON(summary)
	}
	fmt.Fprintf(out, "\n✓ orca init complete — run `orca doctor` to verify.\n")
	return nil
}

func init() {
	rootCmd.AddCommand(initCmd)
}
