#!/usr/bin/env bash

set -euo pipefail

VERSION="0.2.7"

INSTALL_DIR="${INSTALL_DIR:-$HOME/.local/bin}"
WAYNEKERR_DIR="$HOME/.waynekerr"

cat <<BANNER

===================================
 Wayne Kerr Developer Platform
===================================

Installing waynekerr ${VERSION}...

BANNER

mkdir -p "$INSTALL_DIR" "$WAYNEKERR_DIR"
chmod 700 "$WAYNEKERR_DIR"

cat > "$INSTALL_DIR/waynekerr" <<'CLI_EOF'
#!/usr/bin/env bash

set -euo pipefail

VERSION="0.2.7"

WAYNEKERR_DIR="$HOME/.waynekerr"
SESSION_FILE="$WAYNEKERR_DIR/session.json"
CALLBACK_PID_FILE="$WAYNEKERR_DIR/callback.pid"

AUTH_DOMAIN="${WAYNEKERR_AUTH_DOMAIN:-https://login.waynekerr.com}"
CLIENT_ID="${WAYNEKERR_CLIENT_ID:-404nkt3sc422ulo8q6of9sb3l8}"
REDIRECT_URI="${WAYNEKERR_REDIRECT_URI:-http://localhost:8765/callback}"
SCOPES="${WAYNEKERR_SCOPES:-openid email profile}"
API_BASE="${WAYNEKERR_API_BASE:-https://api.waynekerr.com}"
CALLBACK_PORT="${WAYNEKERR_CALLBACK_PORT:-8765}"
CALLBACK_PATH="${WAYNEKERR_CALLBACK_PATH:-/callback}"
LOGIN_TIMEOUT_SECONDS="${WAYNEKERR_LOGIN_TIMEOUT_SECONDS:-300}"

show_help() {
cat <<HELP

Wayne Kerr Developer Platform

Usage:

  waynekerr version
  waynekerr doctor
  waynekerr login
  waynekerr logout
  waynekerr cleanup
  waynekerr uninstall
  waynekerr whoami
  waynekerr install programmer

Environment configuration:

  WAYNEKERR_AUTH_DOMAIN   ${AUTH_DOMAIN}
  WAYNEKERR_CLIENT_ID     ${CLIENT_ID}
  WAYNEKERR_REDIRECT_URI  ${REDIRECT_URI}
  WAYNEKERR_API_BASE      ${API_BASE}

HELP
}

need_cmd() {
  if ! command -v "$1" >/dev/null 2>&1; then
    echo "Missing required command: $1" >&2
    return 1
  fi
}

doctor() {
  echo "Running diagnostics..."
  echo ""

  local failed=0
  for cmd in git node npm python3 curl jq openssl; do
    if command -v "$cmd" >/dev/null 2>&1; then
      echo "✅ $cmd found"
    else
      echo "❌ $cmd missing"
      failed=1
    fi
  done

  if [ -z "$CLIENT_ID" ]; then
    echo "❌ Cognito client ID is not configured"
    failed=1
  else
    echo "✅ Cognito client ID configured"
  fi

  return "$failed"
}

base64_urlencode() {
  openssl base64 -A | tr '+/' '-_' | tr -d '='
}

random_urlsafe() {
  openssl rand -base64 "$1" | tr '+/' '-_' | tr -d '=\n'
}

urlencode() {
  python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read().strip(), safe=""))'
}

jwt_payload_json() {
  local token="$1"
  python3 - "$token" <<'PY'
import base64
import json
import sys

try:
    token = sys.argv[1]
    payload = token.split('.')[1]
    payload += '=' * (-len(payload) % 4)
    print(json.dumps(json.loads(base64.urlsafe_b64decode(payload)), indent=2))
except Exception as exc:
    print(f"Failed to decode JWT payload: {exc}", file=sys.stderr)
    sys.exit(1)
PY
}

open_browser() {
  local url="$1"

  if command -v open >/dev/null 2>&1; then
    open "$url" >/dev/null 2>&1 || true
  elif command -v xdg-open >/dev/null 2>&1; then
    xdg-open "$url" >/dev/null 2>&1 || true
  elif command -v cmd.exe >/dev/null 2>&1; then
    cmd.exe /c start "$url" >/dev/null 2>&1 || true
  else
    return 0
  fi
}

port_available() {
  python3 - "$CALLBACK_PORT" <<'PY'
import socket
import sys

port = int(sys.argv[1])
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

try:
    sock.bind(("127.0.0.1", port))
except OSError:
    sys.exit(1)
finally:
    sock.close()

sys.exit(0)
PY
}

stop_callback_server() {
  local pids=""
  local pid
  local cmd

  if [ -f "$CALLBACK_PID_FILE" ]; then
    pids="$pids $(cat "$CALLBACK_PID_FILE" 2>/dev/null || true)"
  fi

  if command -v lsof >/dev/null 2>&1; then
    pids="$pids $(lsof -tiTCP:"$CALLBACK_PORT" -sTCP:LISTEN 2>/dev/null || true)"
  fi

  for pid in $pids; do
    case "$pid" in
      ''|*[!0-9]*)
        continue
        ;;
    esac

    if ! kill -0 "$pid" >/dev/null 2>&1; then
      continue
    fi

    cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"

    case "$cmd" in
      *"python"*"$CALLBACK_PORT"*"$CALLBACK_PATH"*|*"python"*"$CALLBACK_PORT"*)
        kill "$pid" >/dev/null 2>&1 || true
        ;;
    esac
  done

  rm -f "$CALLBACK_PID_FILE"
}

start_callback_server() {
  local state="$1"
  local output_file="$2"

  python3 - "$CALLBACK_PORT" "$CALLBACK_PATH" "$state" "$output_file" <<'PY' >/dev/null 2>&1 &
import http.server
import json
import socketserver
import sys
import urllib.parse

port = int(sys.argv[1])
callback_path = sys.argv[2]
expected_state = sys.argv[3]
output_file = sys.argv[4]

class Handler(http.server.BaseHTTPRequestHandler):
    def log_message(self, fmt, *args):
        return

    def do_GET(self):
        parsed = urllib.parse.urlparse(self.path)
        params = urllib.parse.parse_qs(parsed.query)

        result = {
            "path": parsed.path,
            "code": params.get("code", [None])[0],
            "state": params.get("state", [None])[0],
            "error": params.get("error", [None])[0],
            "error_description": params.get("error_description", [None])[0],
        }

        status = 200
        message = "Login complete. You can close this tab now and return to your terminal. Run waynekerr whoami to confirm your session."

        if parsed.path != callback_path:
            status = 404
            message = "Unexpected callback path. Return to your terminal."
            result["error"] = "bad_callback_path"
        elif result["error"]:
            status = 400
            message = "Login failed. Return to your terminal."
        elif not result["code"]:
            status = 400
            message = "Missing authorization code. Return to your terminal."
            result["error"] = "missing_code"
        elif result["state"] != expected_state:
            status = 400
            message = "State mismatch. Return to your terminal."
            result["error"] = "state_mismatch"

        with open(output_file, "w", encoding="utf-8") as f:
            json.dump(result, f)

        body = f"""<!doctype html>
<html>
<head>
  <title>Wayne Kerr Login</title>
</head>
<body style="font-family: system-ui, sans-serif; margin: 3rem;">
  <h1>Wayne Kerr Login</h1>
  <p>{message}</p>
</body>
</html>
""".encode("utf-8")

        self.send_response(status)
        self.send_header("Content-Type", "text/html; charset=utf-8")
        self.send_header("Content-Length", str(len(body)))
        self.end_headers()
        self.wfile.write(body)

socketserver.TCPServer.allow_reuse_address = True

with socketserver.TCPServer(("127.0.0.1", port), Handler) as httpd:
    httpd.handle_request()
PY

  echo $!
}

login() {
  need_cmd python3
  need_cmd curl
  need_cmd jq
  need_cmd openssl

  mkdir -p "$WAYNEKERR_DIR"
  chmod 700 "$WAYNEKERR_DIR"

  local code_verifier code_challenge state callback_file server_pid auth_url
  code_verifier="$(random_urlsafe 48)"
  code_challenge="$(printf '%s' "$code_verifier" | openssl dgst -binary -sha256 | base64_urlencode)"
  state="$(random_urlsafe 32)"
  callback_file="$(mktemp)"

  if ! port_available; then
    echo "localhost port ${CALLBACK_PORT} is already in use."
    echo "Trying to clean up a stale Wayne Kerr callback server..."
    stop_callback_server
    sleep 1
  fi

  if ! port_available; then
    echo "Login failed: localhost port ${CALLBACK_PORT} is still in use." >&2
    echo "" >&2
    echo "To inspect it, run:" >&2
    echo "  lsof -nP -iTCP:${CALLBACK_PORT} -sTCP:LISTEN" >&2
    echo "" >&2
    echo "If you are sure it is safe to stop that listener, run:" >&2
    echo "  waynekerr cleanup --force" >&2
    echo "" >&2
    echo "Or use a different callback port, but that port must also be registered in Cognito:" >&2
    echo "  WAYNEKERR_CALLBACK_PORT=8766 WAYNEKERR_REDIRECT_URI=http://localhost:8766/callback waynekerr login" >&2
    exit 1
  fi

  server_pid="$(start_callback_server "$state" "$callback_file")"
  printf '%s\n' "$server_pid" > "$CALLBACK_PID_FILE"
  chmod 600 "$CALLBACK_PID_FILE"

  cleanup_login_callback() {
    kill "$server_pid" >/dev/null 2>&1 || true
    rm -f "$callback_file" "$CALLBACK_PID_FILE"
  }

  trap cleanup_login_callback EXIT

  auth_url="${AUTH_DOMAIN}/oauth2/authorize?response_type=code&client_id=$(printf '%s' "$CLIENT_ID" | urlencode)&redirect_uri=$(printf '%s' "$REDIRECT_URI" | urlencode)&scope=$(printf '%s' "$SCOPES" | urlencode)&state=$(printf '%s' "$state" | urlencode)&code_challenge=$(printf '%s' "$code_challenge" | urlencode)&code_challenge_method=S256"

  echo "Opening Wayne Kerr login in your browser..."
  open_browser "$auth_url"
  echo ""
  echo "If the browser did not open, visit:"
  echo "$auth_url"
  echo ""
  echo "Waiting for authentication..."

  local waited=0
  while [ ! -s "$callback_file" ]; do
    if ! kill -0 "$server_pid" >/dev/null 2>&1; then
      echo "Login failed: callback server stopped unexpectedly." >&2
      exit 1
    fi
    if [ "$waited" -ge "$LOGIN_TIMEOUT_SECONDS" ]; then
      echo "Login timed out." >&2
      exit 1
    fi
    sleep 1
    waited=$((waited + 1))
  done

  local err err_desc code returned_state
  err="$(jq -r '.error // empty' "$callback_file")"
  err_desc="$(jq -r '.error_description // empty' "$callback_file")"
  code="$(jq -r '.code // empty' "$callback_file")"
  returned_state="$(jq -r '.state // empty' "$callback_file")"

  if [ -n "$err" ]; then
    echo "Login failed: $err ${err_desc}" >&2
    exit 1
  fi

  if [ "$returned_state" != "$state" ]; then
    echo "Login failed: state mismatch" >&2
    exit 1
  fi

  local token_response
  token_response="$(curl -fsS -X POST "${AUTH_DOMAIN}/oauth2/token" \
    -H 'Content-Type: application/x-www-form-urlencoded' \
    --data-urlencode 'grant_type=authorization_code' \
    --data-urlencode "client_id=${CLIENT_ID}" \
    --data-urlencode "code=${code}" \
    --data-urlencode "redirect_uri=${REDIRECT_URI}" \
    --data-urlencode "code_verifier=${code_verifier}")"

  local now expires_at id_payload email name groups
  now="$(date +%s)"
  expires_at="$((now + $(printf '%s' "$token_response" | jq -r '.expires_in // 3600')))"
  id_payload="$(jwt_payload_json "$(printf '%s' "$token_response" | jq -r '.id_token')")"
  email="$(printf '%s' "$id_payload" | jq -r '.email // empty')"
  name="$(printf '%s' "$id_payload" | jq -r 'if (.name // "") != "" then .name elif ((.given_name // "") != "" or (.family_name // "") != "") then (((.given_name // "") + " " + (.family_name // "")) | gsub("^ +| +$"; "")) else "" end')"
  groups="$(printf '%s' "$id_payload" | jq -c '(."cognito:groups" // .groups // [])')"

  jq -n \
    --arg email "$email" \
    --arg name "$name" \
    --arg access_token "$(printf '%s' "$token_response" | jq -r '.access_token')" \
    --arg id_token "$(printf '%s' "$token_response" | jq -r '.id_token')" \
    --arg refresh_token "$(printf '%s' "$token_response" | jq -r '.refresh_token // empty')" \
    --arg token_type "$(printf '%s' "$token_response" | jq -r '.token_type // "Bearer"')" \
    --argjson expires_at "$expires_at" \
    --argjson groups "$groups" \
    '{
      email: $email,
      name: $name,
      groups: $groups,
      access_token: $access_token,
      id_token: $id_token,
      refresh_token: $refresh_token,
      expires_at: $expires_at,
      token_type: $token_type
    }' \
    > "$SESSION_FILE"

  chmod 600 "$SESSION_FILE"

  echo ""
  echo "Login successful."
  echo ""
  echo "Signed in as:"
  echo "${email:-unknown}"

  cleanup_login_callback
  trap - EXIT
}

logout() {
  rm -f "$SESSION_FILE"
  stop_callback_server
  echo "Logged out."
}

cleanup() {
  local mode="${1:-}"

  stop_callback_server

  if port_available; then
    echo "Wayne Kerr callback cleanup complete."
    echo "Port ${CALLBACK_PORT} is free."
    return 0
  fi

  echo "Port ${CALLBACK_PORT} is still in use."

  if [ "$mode" != "--force" ]; then
    echo ""
    echo "The process does not clearly look like a stale Wayne Kerr callback server."
    echo "To inspect it, run:"
    echo "  lsof -nP -iTCP:${CALLBACK_PORT} -sTCP:LISTEN"
    echo ""
    echo "If you are sure it is safe to stop, run:"
    echo "  waynekerr cleanup --force"
    return 1
  fi

  if ! command -v lsof >/dev/null 2>&1; then
    echo "Cannot force cleanup because lsof is not installed." >&2
    return 1
  fi

  local pids pid
  pids="$(lsof -tiTCP:"$CALLBACK_PORT" -sTCP:LISTEN 2>/dev/null || true)"

  if [ -z "$pids" ]; then
    echo "No listener found on port ${CALLBACK_PORT}."
    return 0
  fi

  for pid in $pids; do
    case "$pid" in
      ''|*[!0-9]*)
        continue
        ;;
    esac

    echo "Stopping process ${pid} on port ${CALLBACK_PORT}..."
    kill "$pid" >/dev/null 2>&1 || true
  done

  sleep 1

  if ! port_available; then
    echo "Port ${CALLBACK_PORT} is still in use. Trying force kill..."
    for pid in $pids; do
      case "$pid" in
        ''|*[!0-9]*)
          continue
          ;;
      esac
      kill -9 "$pid" >/dev/null 2>&1 || true
    done
  fi

  if port_available; then
    echo "Port ${CALLBACK_PORT} is free."
  else
    echo "Cleanup failed. Inspect the port manually:" >&2
    echo "  lsof -nP -iTCP:${CALLBACK_PORT} -sTCP:LISTEN" >&2
    return 1
  fi
}


uninstall() {
  local assume_yes=0
  local keep_data=0
  local arg

  for arg in "$@"; do
    case "$arg" in
      --yes|-y)
        assume_yes=1
        ;;
      --keep-data)
        keep_data=1
        ;;
      *)
        echo "Unknown uninstall option: $arg" >&2
        echo "" >&2
        echo "Usage:" >&2
        echo "  waynekerr uninstall [--yes] [--keep-data]" >&2
        return 1
        ;;
    esac
  done

  local cli_path
  cli_path="$(command -v waynekerr 2>/dev/null || true)"

  if [ -z "$cli_path" ]; then
    cli_path="$0"
  fi

  echo "This will uninstall Wayne Kerr CLI."
  echo ""
  echo "Will remove:"
  echo "  CLI: ${cli_path}"

  if [ "$keep_data" -eq 0 ]; then
    echo "  Data: ${WAYNEKERR_DIR}"
  else
    echo "  Data: kept because --keep-data was provided"
  fi

  echo ""

  if [ "$assume_yes" -ne 1 ]; then
    local answer
    printf "Continue? [y/N] "
    read -r answer
    case "$answer" in
      y|Y|yes|YES)
        ;;
      *)
        echo "Uninstall cancelled."
        return 0
        ;;
    esac
  fi

  stop_callback_server

  if [ "$keep_data" -eq 0 ]; then
    rm -rf "$WAYNEKERR_DIR"
  else
    rm -f "$CALLBACK_PID_FILE"
  fi

  if [ -n "$cli_path" ] && [ -f "$cli_path" ]; then
    rm -f "$cli_path"
  fi

  echo ""
  echo "Wayne Kerr CLI uninstalled."
  echo ""
  echo "Run this so your shell forgets the old command path:"
  echo "  hash -r"
}

require_session() {
  if [ ! -f "$SESSION_FILE" ]; then
    echo "You are not logged in."
    echo ""
    echo "Run:"
    echo "  waynekerr login"
    exit 1
  fi

  local now expires_at
  now="$(date +%s)"
  expires_at="$(jq -r '.expires_at // 0' "$SESSION_FILE")"

  if [ "$expires_at" -le "$now" ]; then
    echo "Your session has expired."
    echo ""
    echo "Run:"
    echo "  waynekerr login"
    exit 1
  fi
}

whoami() {
  require_session
  echo "Email: $(jq -r '.email // ""' "$SESSION_FILE")"
  echo "Name: $(jq -r '.name // ""' "$SESSION_FILE")"
  echo "Groups: $(jq -r '(.groups // []) | join(", ")' "$SESSION_FILE")"
}

install_programmer() {
  require_session
  need_cmd curl
  need_cmd jq

  echo "Checking authentication..."
  echo "Fetching programmer profile..."

  local access_token profile
  access_token="$(jq -r '.access_token' "$SESSION_FILE")"
  profile="$(curl -fsS "${API_BASE}/profiles/programmer" -H "Authorization: Bearer ${access_token}")"

  echo "Installing required tools..."
  echo "$profile" | jq .
  echo "Done."
}

case "${1:-}" in
  version)
    echo "waynekerr ${VERSION}"
    ;;
  doctor)
    doctor
    ;;
  login)
    login
    ;;
  logout)
    logout
    ;;
  cleanup)
    cleanup "${2:-}"
    ;;
  uninstall)
    uninstall "${@:2}"
    ;;
  whoami)
    whoami
    ;;
  install)
    case "${2:-}" in
      programmer)
        install_programmer
        ;;
      *)
        echo "Unknown profile."
        exit 1
        ;;
    esac
    ;;
  *)
    show_help
    ;;
esac
CLI_EOF

chmod +x "$INSTALL_DIR/waynekerr"

if ! printf '%s' "$PATH" | grep -q "$INSTALL_DIR"; then
  cat <<PATH_MSG

Add this to your shell profile:

export PATH="$INSTALL_DIR:\$PATH"

PATH_MSG
fi

cat <<DONE

Installation complete.

Try:

  waynekerr version
  waynekerr doctor
  waynekerr login

DONE
