package main

import (
	"flag"
	"fmt"
	"os"
	"strings"

	config "astrology-api/configs"
	"astrology-api/notifications"
)

// A standalone check that Firebase push actually works, without having to
// drive a wallet recharge or a live consultation to find out.
//
// Two modes:
//
//	go run ./cmd/notifytest -token <device-fcm-token>
//	    Sends a test notification straight to one device. No database is
//	    touched, so this works before anything else is set up. Use it first —
//	    if this fails, nothing else will.
//
//	go run ./cmd/notifytest -event wallet -user 7
//	    Sends a real product notification to a real account, resolving the
//	    device tokens from the database exactly as the API does at runtime.
//	    Needs .env and a reachable MySQL.
//
// Exit status is 0 only when a message was accepted by FCM, so this can be
// used in a deployment check.
func main() {

	token := flag.String("token", "", "device FCM token to send a test notification to")

	event := flag.String("event", "", "product notification to send: wallet | online | request | low-balance | kundali | settled | withdraw-approved | admin")

	userID := flag.Uint("user", 0, "target users.id (for customer events)")

	astrologerID := flag.Uint("astrologer", 0, "target astrologers.id (for astrologer events)")

	title := flag.String("title", "Test notification", "title, for -token and -event admin")

	body := flag.String("body", "If you can read this, Firebase push is working.", "body, for -token and -event admin")

	inspect := flag.Bool("inspect", false, "list the device tokens a user or astrologer would be notified on, and send nothing")

	flag.Parse()

	//------------------------------------------------
	// Configuration
	//------------------------------------------------

	// Tolerant already: LoadEnv falls back to the process environment when
	// there is no .env, and only gives up when neither is populated.
	config.LoadEnv()

	fmt.Println("project id :", firstNonEmpty(os.Getenv("FIREBASE_PROJECT_ID"), "(from credentials)"))
	fmt.Println("credentials:", credentialsSource())

	// -inspect only reads the database, so it runs before the credentials
	// gate: "which tokens does this account have?" is exactly the question
	// worth asking while Firebase is still unconfigured.
	if *inspect {
		runInspect(*userID, *astrologerID)
		return
	}

	if !notifications.Configured() {

		fmt.Println()
		fmt.Println("FAILED: Firebase is not configured.")
		fmt.Println(notifications.ConfigError())
		fmt.Println()
		fmt.Println("Fix it like this:")
		fmt.Println("  1. Firebase Console > Project settings > Service accounts")
		fmt.Println("     > Generate new private key. Save the JSON outside the repo.")
		fmt.Println("  2. Add to .env:")
		fmt.Println("       FIREBASE_PROJECT_ID=astroeye-1")
		fmt.Println("       FIREBASE_CREDENTIALS_FILE=C:/secrets/astroeye-service-account.json")
		fmt.Println("  3. Re-run this command.")

		os.Exit(1)
	}

	fmt.Println("status     : credentials loaded and private key parsed")

	//------------------------------------------------
	// Direct send
	//------------------------------------------------

	if strings.TrimSpace(*token) != "" {

		fmt.Println()
		fmt.Println("sending directly to the given device token...")

		if err := notifications.SendTest(*token, *title, *body); err != nil {

			fmt.Println("FAILED:", err)
			fmt.Println()
			fmt.Println("A 404 UNREGISTERED means the token is stale — copy a fresh one from the app.")
			fmt.Println("A 401 or 403 means the service account cannot send to this project.")

			os.Exit(1)
		}

		fmt.Println("OK: FCM accepted the message. It should arrive on the device now.")

		return
	}

	//------------------------------------------------
	// Inspect
	//------------------------------------------------
	//
	// Answers "why did nothing arrive?" before blaming Firebase: an account
	// with no registered token cannot be notified, however well configured
	// the credentials are.

	//------------------------------------------------
	// Product notification
	//------------------------------------------------

	if strings.TrimSpace(*event) == "" {

		fmt.Println()
		fmt.Println("Nothing to send. Pass -token <fcm-token> for a direct test,")
		fmt.Println("or -event <name> with -user / -astrologer for a real product notification.")
		fmt.Println()
		flag.Usage()

		os.Exit(2)
	}

	// The product notifications resolve their audience from the database.
	config.ConnectDB()

	notifications.Init(config.DB)

	notifier := notifications.Notify()

	switch strings.ToLower(strings.TrimSpace(*event)) {

	case "wallet":

		requireUser(*userID)
		notifier.WalletRecharged(*userID, 500, 1250.50)

	case "online":

		requireAstrologer(*astrologerID)
		notifier.FavouriteAstrologerOnline(*astrologerID)

	case "request":

		requireAstrologer(*astrologerID)
		notifier.ConsultationRequestReceived(*astrologerID, *userID, "CHAT", 0, "TEST-0001")

	case "low-balance":

		requireUser(*userID)
		notifier.LowBalanceWarning(*userID, "CHAT", 0, 30)

	case "kundali":

		requireUser(*userID)
		notifier.KundaliGenerated(*userID, 0, "Test Kundali")

	case "settled":

		requireAstrologer(*astrologerID)
		notifier.SettlementCredited(*astrologerID, 1640, 4890, 6, "TEST-STL-0001")

	case "withdraw-approved":

		requireAstrologer(*astrologerID)
		notifier.WithdrawApproved(*astrologerID, 2000, "TEST-REF-0001")

	case "admin":

		if *userID > 0 {
			notifier.AdminMessageToCustomer(*userID, *title, *body)
		} else {
			requireAstrologer(*astrologerID)
			notifier.AdminMessageToAstrologer(*astrologerID, *title, *body)
		}

	default:

		fmt.Println("unknown -event:", *event)
		os.Exit(2)
	}

	// Dispatch is asynchronous by design, so wait for the in-flight sends
	// rather than exiting out from under them.
	notifications.Drain()

	fmt.Println()
	fmt.Println("OK: notification dispatched. Check the device, and user_notifications for the row.")
	fmt.Println("Any per-device failure is printed above by the notifier.")
}

// runInspect answers "why did nothing arrive?" before Firebase gets the
// blame: an account with no registered token cannot be notified, however well
// configured the credentials are.
func runInspect(userID uint, astrologerID uint) {

	config.ConnectDB()

	notifications.Init(config.DB)

	target := userID

	if target == 0 && astrologerID > 0 {

		target = notifications.Notify().AstrologerUserID(astrologerID)

		fmt.Printf("astrologer %d maps to users.id %d\n", astrologerID, target)
	}

	if target == 0 {
		fmt.Println("pass -user <users.id> or -astrologer <astrologers.id>")
		os.Exit(2)
	}

	tokens := notifications.Notify().TokensFor(target)

	fmt.Printf("registered device tokens for users.id %d: %d\n", target, len(tokens))

	for _, value := range tokens {
		fmt.Printf("  %s (length %d)\n", preview(value), len(value))
	}

	if len(tokens) == 0 {
		fmt.Println()
		fmt.Println("No tokens. The app must send its FCM token to the API —")
		fmt.Println("on register, on OTP verification, at login, or through")
		fmt.Println("POST /api/user/device-token — before this account can")
		fmt.Println("receive a push.")
	}
}

func requireUser(userID uint) {

	if userID == 0 {
		fmt.Println("this event needs -user <users.id>")
		os.Exit(2)
	}
}

func requireAstrologer(astrologerID uint) {

	if astrologerID == 0 {
		fmt.Println("this event needs -astrologer <astrologers.id>")
		os.Exit(2)
	}
}

// preview shows enough of a token to recognise it without printing a
// credential in full to a terminal or a log.
func preview(token string) string {

	if len(token) <= 14 {
		return "(too short to be a real FCM token)"
	}

	return token[:14] + "..."
}

func credentialsSource() string {

	if value := strings.TrimSpace(os.Getenv("FIREBASE_CREDENTIALS_JSON")); value != "" {
		return "FIREBASE_CREDENTIALS_JSON (inline)"
	}

	if value := strings.TrimSpace(os.Getenv("FIREBASE_CREDENTIALS_FILE")); value != "" {
		return value
	}

	return "(not set)"
}

func firstNonEmpty(values ...string) string {

	for _, value := range values {

		if strings.TrimSpace(value) != "" {
			return value
		}
	}

	return ""
}
