package auth

import (
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/mrhid6/vantage/admin/internal/audit"
	"github.com/mrhid6/vantage/admin/internal/db"
	adminmodels "github.com/mrhid6/vantage/admin/internal/models"
	sharedmodels "github.com/mrhid6/vantage/shared/models"
	"go.mongodb.org/mongo-driver/v2/bson"
	"golang.org/x/crypto/bcrypt"
)

// HandleCloudLogin authenticates a cloud customer against the CONTROL PLANE's
// users collection, with the credentials they already have.
//
// Two consequences worth stating plainly, because they are real and were
// accepted deliberately:
//
//  1. A cloud user's control-plane password now also unlocks billing. Any
//     password change or compromise has a wider blast radius than before.
//  2. Only control-plane role "owner" may sign in here. admin and member are
//     refused — billing is an owner concern.
//
// Mitigations: rate limits, an identical error for every failure, and an audit
// entry for every attempt.
func HandleCloudLogin(c *gin.Context) {
	var body struct {
		Email    string `json:"email"`
		Password string `json:"password"`
	}
	if err := c.ShouldBindJSON(&body); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": "email and password are required"})
		return
	}
	email := strings.ToLower(strings.TrimSpace(body.Email))
	ctx := c.Request.Context()

	if !allowAttempt(email, c.ClientIP()) {
		c.JSON(http.StatusTooManyRequests, gin.H{"error": "too many attempts, try again later"})
		return
	}

	reject := func(reason string) {
		audit.Write(ctx, adminmodels.AuditEntry{
			Actor: email, Action: "cloud.login_failed", IP: c.ClientIP(), Detail: reason})
		c.JSON(http.StatusUnauthorized, gin.H{"error": genericAuthError})
	}

	// A self-hosted customer_users row wins over a control-plane user with the
	// same address. Documented so the behaviour is chosen rather than emergent.
	if n, _ := db.Admin("customer_users").CountDocuments(ctx, bson.M{"email": email}); n > 0 {
		HandleCustomerLogin(c)
		return
	}

	var u sharedmodels.User
	if err := db.Control("users").FindOne(ctx, bson.M{"email": email}).Decode(&u); err != nil {
		bcrypt.CompareHashAndPassword([]byte(dummyHash), []byte(body.Password))
		reject("unknown email")
		return
	}
	if bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(body.Password)) != nil {
		reject("bad password")
		return
	}
	if u.Role != sharedmodels.RoleOwner {
		reject("role " + u.Role + " is not permitted")
		return
	}

	// Resolve the admin-side account that owns this user's instance.
	var inst adminmodels.Instance
	if err := db.Admin("admin_instances").FindOne(ctx,
		bson.M{"instance_id": u.InstanceID}).Decode(&inst); err != nil {
		reject("no account for instance " + u.InstanceID)
		return
	}

	id, err := Save(ctx, Session{
		UserID: u.UserID, Kind: KindCustomer, Email: u.Email, AccountID: inst.AccountID,
	})
	if err != nil {
		c.JSON(http.StatusInternalServerError, gin.H{"error": "session failed"})
		return
	}
	SetCookie(c, id)
	clearAttempts(email)
	audit.Write(ctx, adminmodels.AuditEntry{
		Actor: email, Action: "cloud.login", AccountID: inst.AccountID, IP: c.ClientIP()})
	c.JSON(http.StatusOK, gin.H{"kind": KindCustomer, "email": u.Email})
}
