#!/usr/bin/env bash
# init_api_app
# ------------
# Scaffold de una Go HTTP API app en apps/{nombre}/.
#
# Genera main.go, handlers.go, config.go, migrations/001_initial.sql,
# Makefile, .env.example, .gitignore y app.md con frontmatter correcto.
# El boilerplate importa funciones del registry (http_serve, http_router,
# http_middleware_chain, migration_up, etc.) y verifica que compila con
# `go vet` al final.
#
# USO:
#   ./init_api_app.sh <nombre> [--port N] [--with-auth] [--with-db] [--with-ops]
#
# FLAGS:
#   --port N       Puerto por defecto (default: 8080)
#   --with-auth    Anade JWT middleware, login/register, tabla users
#   --with-db      Anade store.go con helpers CRUD y setup de SQLite
#   --with-ops     Anade `fn ops init` para crear operations.db con schema completo
#
# EJEMPLO:
#   ./init_api_app.sh my_service
#   ./init_api_app.sh billing_api --port 8090 --with-auth --with-db

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REGISTRY_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"

# Source funciones atomicas del registry
source "$REGISTRY_ROOT/bash/functions/shell/assert_command_exists.sh"

# ── Parsing de argumentos ────────────────────────────────────

NOMBRE=""
PORT="8080"
WITH_AUTH="false"
WITH_DB="false"
WITH_OPS="false"

while [ $# -gt 0 ]; do
    case "$1" in
        --port)
            PORT="$2"; shift 2 ;;
        --with-auth)
            WITH_AUTH="true"; shift ;;
        --with-db)
            WITH_DB="true"; shift ;;
        --with-ops)
            WITH_OPS="true"; shift ;;
        -h|--help)
            grep "^#" "$0" | sed 's/^# \?//' ; exit 0 ;;
        -*)
            echo "Flag desconocido: $1" >&2 ; exit 1 ;;
        *)
            if [ -z "$NOMBRE" ]; then
                NOMBRE="$1"
            else
                echo "Argumento extra ignorado: $1" >&2
            fi
            shift ;;
    esac
done

if [ -z "$NOMBRE" ]; then
    echo "Uso: $0 <nombre> [--port N] [--with-auth] [--with-db] [--with-ops]" >&2
    exit 1
fi

APP_DIR="${REGISTRY_ROOT}/apps/${NOMBRE}"

if [ -d "$APP_DIR" ]; then
    echo "ERROR: ${APP_DIR} ya existe. Abortando para no sobrescribir." >&2
    exit 1
fi

echo ""
echo "════════════════════════════════════════════════════════════"
echo "  INIT API APP: ${NOMBRE}"
echo "  Directorio: ${APP_DIR}"
echo "  Puerto:     ${PORT}"
echo "  Auth:       ${WITH_AUTH}"
echo "  DB:         ${WITH_DB}"
echo "  Ops:        ${WITH_OPS}"
echo "════════════════════════════════════════════════════════════"
echo ""

# ── 1. Verificar Go ──────────────────────────────────────────

echo "[1/7] Verificando herramientas..."
assert_command_exists go
echo "  Go: $(go version)"

# ── 2. Crear estructura ──────────────────────────────────────

echo "[2/7] Creando estructura..."
mkdir -p "$APP_DIR/migrations"
echo "  ${APP_DIR}/"
echo "  ${APP_DIR}/migrations/"

# ── 3. Escribir go.mod ───────────────────────────────────────

echo "[3/7] Creando go.mod..."
cat > "$APP_DIR/go.mod" <<EOF
module ${NOMBRE}

go 1.25.0

require (
	fn-registry v0.0.0-00010101000000-000000000000
	github.com/mattn/go-sqlite3 v1.14.37
)

replace fn-registry => ${REGISTRY_ROOT}
EOF

# ── 4. Escribir archivos Go ──────────────────────────────────

echo "[4/7] Escribiendo archivos Go..."

# config.go
cat > "$APP_DIR/config.go" <<'EOF'
package main

import (
	"os"
)

// Config contiene la configuracion runtime de la app.
type Config struct {
	AppName     string
	Port        string
	DBPath      string
	CORSOrigins []string
}

// LoadConfig lee la configuracion desde variables de entorno con defaults sensatos.
func LoadConfig() Config {
	return Config{
		AppName:     getenv("APP_NAME", "__APP_NAME__"),
		Port:        getenv("PORT", "__PORT__"),
		DBPath:      getenv("DB_PATH", "__APP_NAME__.db"),
		CORSOrigins: []string{getenv("CORS_ORIGINS", "*")},
	}
}

func getenv(key, def string) string {
	if v := os.Getenv(key); v != "" {
		return v
	}
	return def
}
EOF
sed -i "s/__APP_NAME__/${NOMBRE}/g; s/__PORT__/${PORT}/g" "$APP_DIR/config.go"

# handlers.go
cat > "$APP_DIR/handlers.go" <<'EOF'
package main

import (
	"net/http"

	"fn-registry/functions/infra"
)

// healthHandler responde {"status":"ok"} en GET /health.
func healthHandler(w http.ResponseWriter, r *http.Request) {
	infra.HTTPJSONResponse(w, http.StatusOK, map[string]string{
		"status": "ok",
	})
}

// statusHandler es un handler de ejemplo en GET /api/v1/status.
func statusHandler(w http.ResponseWriter, r *http.Request) {
	infra.HTTPJSONResponse(w, http.StatusOK, map[string]any{
		"app":     "__APP_NAME__",
		"version": "0.1.0",
	})
}
EOF
sed -i "s/__APP_NAME__/${NOMBRE}/g" "$APP_DIR/handlers.go"

# main.go
# Construccion incremental segun flags
MAIN_IMPORTS='"context"
	"database/sql"
	"log"
	"os"
	"os/signal"
	"syscall"

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

	"fn-registry/functions/infra"'

MAIN_MIGRATION=""
if [ "$WITH_DB" = "true" ]; then
    MAIN_MIGRATION='	// Migrations
	if _, err := infra.MigrationUp(db, "migrations"); err != nil {
		log.Fatalf("migrations: %v", err)
	}
'
else
    MAIN_MIGRATION='	// Migrations (only if migrations dir has content)
	if _, err := os.Stat("migrations"); err == nil {
		if _, err := infra.MigrationUp(db, "migrations"); err != nil {
			log.Fatalf("migrations: %v", err)
		}
	}
'
fi

cat > "$APP_DIR/main.go" <<EOF
package main

import (
	${MAIN_IMPORTS}
)

func main() {
	cfg := LoadConfig()

	// DB
	db, err := sql.Open("sqlite3", cfg.DBPath)
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	defer db.Close()

${MAIN_MIGRATION}
	// Routes
	routes := []infra.Route{
		{Method: "GET", Path: "/health", Handler: http.HandlerFunc(healthHandler)},
		{Method: "GET", Path: "/api/v1/status", Handler: http.HandlerFunc(statusHandler)},
	}
	mux := infra.HTTPRouter(routes)

	// Middleware chain (outer → inner)
	chain := infra.HTTPMiddlewareChain(
		infra.HTTPLoggerMiddleware(os.Stdout),
		infra.HTTPCORSMiddleware(cfg.CORSOrigins, []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
	)
	handler := chain(mux)

	// Graceful shutdown via context
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	addr := ":" + cfg.Port
	log.Printf("starting %s on %s", cfg.AppName, addr)
	if err := infra.HTTPServe(addr, handler, ctx); err != nil {
		log.Fatalf("http serve: %v", err)
	}
	log.Println("server stopped")
}
EOF

# main.go necesita import net/http tambien
# rehacerlo con el import bien
cat > "$APP_DIR/main.go" <<EOF
package main

import (
	"context"
	"database/sql"
	"log"
	"net/http"
	"os"
	"os/signal"
	"syscall"

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

	"fn-registry/functions/infra"
)

func main() {
	cfg := LoadConfig()

	// DB
	db, err := sql.Open("sqlite3", cfg.DBPath)
	if err != nil {
		log.Fatalf("open db: %v", err)
	}
	defer db.Close()

${MAIN_MIGRATION}
	// Routes
	routes := []infra.Route{
		{Method: "GET", Path: "/health", Handler: http.HandlerFunc(healthHandler)},
		{Method: "GET", Path: "/api/v1/status", Handler: http.HandlerFunc(statusHandler)},
	}
	mux := infra.HTTPRouter(routes)

	// Middleware chain
	chain := infra.HTTPMiddlewareChain(
		infra.HTTPLoggerMiddleware(os.Stdout),
		infra.HTTPCORSMiddleware(cfg.CORSOrigins, []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
	)
	handler := chain(mux)

	// Graceful shutdown via signal-aware context
	ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer cancel()

	addr := ":" + cfg.Port
	log.Printf("starting %s on %s", cfg.AppName, addr)
	if err := infra.HTTPServe(addr, handler, ctx); err != nil {
		log.Fatalf("http serve: %v", err)
	}
	log.Println("server stopped")
	_ = db
}
EOF

# --- Auth opcional ---
if [ "$WITH_AUTH" = "true" ]; then
    cat > "$APP_DIR/auth.go" <<'EOF'
package main

import (
	"database/sql"
	"encoding/json"
	"net/http"
	"time"

	"fn-registry/functions/infra"
)

// LoginRequest es el body JSON del endpoint /auth/login.
type LoginRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

// RegisterRequest es el body JSON del endpoint /auth/register.
type RegisterRequest struct {
	Email    string `json:"email"`
	Password string `json:"password"`
}

// authRegisterHandler crea un usuario nuevo.
func authRegisterHandler(db *sql.DB, jwtSecret string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req RegisterRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 400, Code: "bad_request", Message: "invalid body"})
			return
		}
		hash, err := infra.PasswordHash(req.Password, 10)
		if err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 500, Code: "hash_error", Message: err.Error()})
			return
		}
		if _, err := db.Exec(
			`INSERT INTO users (email, password_hash, created_at) VALUES (?, ?, ?)`,
			req.Email, hash, time.Now().Format(time.RFC3339),
		); err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 409, Code: "duplicate_user", Message: err.Error()})
			return
		}
		infra.HTTPJSONResponse(w, http.StatusCreated, map[string]string{"email": req.Email})
	}
}

// authLoginHandler valida credenciales y emite un JWT.
func authLoginHandler(db *sql.DB, jwtSecret string) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		var req LoginRequest
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 400, Code: "bad_request", Message: "invalid body"})
			return
		}
		var id int64
		var hash string
		err := db.QueryRow(`SELECT id, password_hash FROM users WHERE email = ?`, req.Email).Scan(&id, &hash)
		if err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 401, Code: "invalid_credentials", Message: "invalid credentials"})
			return
		}
		if err := infra.PasswordVerify(req.Password, hash); err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 401, Code: "invalid_credentials", Message: "invalid credentials"})
			return
		}
		claims := infra.JWTClaims{
			Subject:   req.Email,
			ExpiresAt: time.Now().Add(24 * time.Hour).Unix(),
			IssuedAt:  time.Now().Unix(),
		}
		token, err := infra.JWTGenerate(claims, jwtSecret)
		if err != nil {
			infra.HTTPErrorResponse(w, infra.HTTPError{Status: 500, Code: "jwt_error", Message: err.Error()})
			return
		}
		infra.HTTPJSONResponse(w, http.StatusOK, map[string]string{"token": token})
	}
}
EOF
fi

# --- Store opcional ---
if [ "$WITH_DB" = "true" ]; then
    cat > "$APP_DIR/store.go" <<'EOF'
package main

import (
	"database/sql"
	"fmt"
)

// Store encapsula el acceso a la base de datos.
type Store struct {
	db *sql.DB
}

// NewStore crea una Store con el pool de conexiones dado.
func NewStore(db *sql.DB) *Store {
	return &Store{db: db}
}

// Ping verifica conectividad con la base de datos.
func (s *Store) Ping() error {
	if err := s.db.Ping(); err != nil {
		return fmt.Errorf("ping: %w", err)
	}
	return nil
}
EOF
fi

# ── 5. Migracion inicial ─────────────────────────────────────

echo "[5/7] Escribiendo migrations/001_initial.sql..."
cat > "$APP_DIR/migrations/001_initial.sql" <<EOF
-- 001_initial — schema inicial para ${NOMBRE}

CREATE TABLE IF NOT EXISTS items (
    id         INTEGER PRIMARY KEY AUTOINCREMENT,
    name       TEXT    NOT NULL,
    created_at TEXT    NOT NULL DEFAULT (datetime('now')),
    updated_at TEXT    NOT NULL DEFAULT (datetime('now'))
);

-- DOWN
DROP TABLE IF EXISTS items;
EOF

if [ "$WITH_AUTH" = "true" ]; then
    cat > "$APP_DIR/migrations/002_users.sql" <<'EOF'
-- 002_users — tabla users para auth

CREATE TABLE IF NOT EXISTS users (
    id             INTEGER PRIMARY KEY AUTOINCREMENT,
    email          TEXT    NOT NULL UNIQUE,
    password_hash  TEXT    NOT NULL,
    created_at     TEXT    NOT NULL DEFAULT (datetime('now'))
);

CREATE TABLE IF NOT EXISTS sessions (
    token      TEXT    PRIMARY KEY,
    user_id    INTEGER NOT NULL,
    metadata   TEXT,
    expires_at TEXT    NOT NULL,
    created_at TEXT    NOT NULL DEFAULT (datetime('now'))
);

-- DOWN
DROP TABLE IF EXISTS sessions;
DROP TABLE IF EXISTS users;
EOF
fi

# ── 6. Makefile / .env.example / .gitignore / app.md ─────────

echo "[6/7] Escribiendo Makefile, .env.example, .gitignore..."

cat > "$APP_DIR/Makefile" <<EOF
.PHONY: build run test vet clean dev

BIN=${NOMBRE}

build:
	CGO_ENABLED=1 go build -tags fts5 -o \$(BIN) .

run: build
	./\$(BIN)

dev:
	CGO_ENABLED=1 go run .

test:
	CGO_ENABLED=1 go test -tags fts5 -v ./...

vet:
	CGO_ENABLED=1 go vet -tags fts5 ./...

clean:
	rm -f \$(BIN) *.db *.db-shm *.db-wal
EOF

cat > "$APP_DIR/.env.example" <<EOF
# Configuracion de ${NOMBRE}
APP_NAME=${NOMBRE}
PORT=${PORT}
DB_PATH=${NOMBRE}.db
CORS_ORIGINS=*
EOF

if [ "$WITH_AUTH" = "true" ]; then
    cat >> "$APP_DIR/.env.example" <<EOF

# Auth
JWT_SECRET=change-me-in-production
EOF
fi

cat > "$APP_DIR/.gitignore" <<EOF
# Binario
${NOMBRE}

# SQLite
*.db
*.db-shm
*.db-wal

# Env
.env

# IDE
.idea/
.vscode/
EOF

# app.md con frontmatter
USES_FUNCTIONS='  - http_serve_go_infra
  - http_router_go_infra
  - http_middleware_chain_go_infra
  - http_cors_middleware_go_infra
  - http_logger_middleware_go_infra
  - http_json_response_go_infra
  - http_error_response_go_infra
  - migration_up_go_infra'

if [ "$WITH_AUTH" = "true" ]; then
    USES_FUNCTIONS="${USES_FUNCTIONS}
  - jwt_generate_go_infra
  - password_hash_go_infra
  - password_verify_go_infra"
fi

cat > "$APP_DIR/app.md" <<EOF
---
name: ${NOMBRE}
lang: go
domain: tools
description: "API HTTP generada por init_api_app."
tags: [service]
uses_functions:
${USES_FUNCTIONS}
uses_types:
  - Route_go_infra
  - Middleware_go_infra
  - HTTPError_go_infra
framework: "net/http"
entry_point: "main.go"
dir_path: "apps/${NOMBRE}"
---

## Notas

App scaffoldeada por \`init_api_app\`. Puerto ${PORT}. Health check en \`/health\`.

Ejecutar: \`make run\` o \`make dev\` para hot reload.
EOF

# ── 7. Verificar con go vet ──────────────────────────────────

echo "[7/7] Verificando con go vet..."
(
    cd "$APP_DIR"
    if go mod tidy 2>&1 | tail -3; then
        :
    fi
    if CGO_ENABLED=1 go vet -tags fts5 ./... 2>&1; then
        echo "  go vet OK"
    else
        echo "  WARN: go vet reporto problemas (revisa el output arriba)" >&2
    fi
)

echo ""
echo "════════════════════════════════════════════════════════════"
echo "  API APP '${NOMBRE}' LISTA"
echo "════════════════════════════════════════════════════════════"
echo ""
echo "  Pasos siguientes:"
echo "    cd apps/${NOMBRE}"
echo "    cp .env.example .env"
echo "    make run"
echo ""
echo "  Health check:"
echo "    curl localhost:${PORT}/health"
echo ""
