package store

import (
	"context"
	"errors"
	"sync"
	"time"

	"github.com/jackc/pgx/v5/pgxpool"
)

const leadershipReleaseTimeout = 5 * time.Second

const (
	identityCoordinatorLeadershipKey    = "easyai-gateway-identity-coordinator"
	securityEventHeartbeatLeadershipKey = "easyai-gateway-security-event-heartbeat"
	capacityControllerLeadershipKey     = "easyai-gateway-capacity-controller"
)

// Leadership holds a PostgreSQL session advisory lock. Callers must keep the
// lease alive and release it when leadership is no longer needed.
type Leadership interface {
	KeepAlive(context.Context) error
	Release()
}

type postgresLeadership struct {
	conn *pgxpool.Conn
	key  string
	once sync.Once
}

func (s *Store) TryAcquireIdentityCoordinatorLeadership(ctx context.Context) (Leadership, bool, error) {
	return s.tryAcquireLeadership(ctx, identityCoordinatorLeadershipKey)
}

func (s *Store) TryAcquireSecurityEventHeartbeatLeadership(ctx context.Context) (Leadership, bool, error) {
	return s.tryAcquireLeadership(ctx, securityEventHeartbeatLeadershipKey)
}

func (s *Store) TryAcquireCapacityControllerLeadership(ctx context.Context) (Leadership, bool, error) {
	return s.tryAcquireLeadership(ctx, capacityControllerLeadershipKey)
}

func (s *Store) TryAcquireRouteProbeLeadership(ctx context.Context, poolID, routeProfileKey string) (Leadership, bool, error) {
	return s.tryAcquireLeadership(ctx, "easyai-gateway-route-probe:"+poolID+":"+routeProfileKey)
}

func (s *Store) tryAcquireLeadership(ctx context.Context, key string) (Leadership, bool, error) {
	conn, err := s.pool.Acquire(ctx)
	if err != nil {
		return nil, false, err
	}
	var acquired bool
	if err := conn.QueryRow(ctx,
		`SELECT pg_try_advisory_lock(hashtextextended($1, 0))`,
		key,
	).Scan(&acquired); err != nil {
		conn.Release()
		return nil, false, err
	}
	if !acquired {
		conn.Release()
		return nil, false, nil
	}
	return &postgresLeadership{conn: conn, key: key}, true, nil
}

func (leadership *postgresLeadership) KeepAlive(ctx context.Context) error {
	if leadership == nil || leadership.conn == nil {
		return errors.New("leadership lease is closed")
	}
	var alive int
	return leadership.conn.QueryRow(ctx, `SELECT 1`).Scan(&alive)
}

func (leadership *postgresLeadership) Release() {
	if leadership == nil {
		return
	}
	leadership.once.Do(func() {
		unlockCtx, unlockCancel := context.WithTimeout(context.Background(), leadershipReleaseTimeout)
		var unlocked bool
		err := leadership.conn.QueryRow(unlockCtx,
			`SELECT pg_advisory_unlock(hashtextextended($1, 0))`,
			leadership.key,
		).Scan(&unlocked)
		unlockCancel()
		if err != nil || !unlocked {
			// A session-scoped advisory lock must never return to the pool when
			// unlock is uncertain. Closing the connection lets PostgreSQL
			// release the lock and forces the pool to replace the session.
			conn := leadership.conn.Hijack()
			closeCtx, closeCancel := context.WithTimeout(context.Background(), leadershipReleaseTimeout)
			_ = conn.Close(closeCtx)
			closeCancel()
		} else {
			leadership.conn.Release()
		}
		leadership.conn = nil
	})
}
