package runtimeworker

import (
	"database/sql"
	"errors"
	"sync"
	"time"

	database "github.com/nexuscrm/db"
)

const (
	workerPoolPurposeQueueObservation = "queue_observation"
	workerPoolPurposeRejectedSave     = "rejected_save_audit"
	workerTransientPurposeObjectID    = "object_id_probe"
)

type auxiliaryDatabasePoolSnapshot struct {
	Purpose      string
	Active       bool
	Stats        sql.DBStats
	OpenAttempts uint64
	OpenFailures uint64
	Closes       uint64
}

type transientDatabaseSnapshot struct {
	Purpose      string
	OpenAttempts uint64
	OpenFailures uint64
	OpenDuration time.Duration
}

// workerAuxiliaryPoolTracker owns lifecycle observation for the one lazy,
// long-lived queue-observation pool in a Worker process.
type workerAuxiliaryPoolTracker struct {
	mu sync.RWMutex

	db           *sql.DB
	active       bool
	openAttempts uint64
	openFailures uint64
	closes       uint64
}

func (t *workerAuxiliaryPoolTracker) open(
	opener func(database.PoolOptions) (*sql.DB, error),
	options database.PoolOptions,
) (*sql.DB, func() error, error) {
	if opener == nil {
		return nil, nil, errors.New("independent database opener is unavailable")
	}
	t.mu.Lock()
	t.openAttempts++
	t.mu.Unlock()
	db, err := opener(options)
	if err != nil || db == nil {
		t.mu.Lock()
		t.openFailures++
		t.mu.Unlock()
		if err == nil {
			err = errors.New("independent database opener returned no pool")
		}
		return nil, nil, err
	}
	t.mu.Lock()
	t.db = db
	t.active = true
	t.mu.Unlock()
	var closeOnce sync.Once
	var closeErr error
	release := func() error {
		closeOnce.Do(func() {
			closeErr = db.Close()
			t.mu.Lock()
			if t.db == db {
				t.active = false
				t.db = nil
			}
			t.closes++
			t.mu.Unlock()
		})
		return closeErr
	}
	return db, release, nil
}

func (t *workerAuxiliaryPoolTracker) snapshot() auxiliaryDatabasePoolSnapshot {
	result := auxiliaryDatabasePoolSnapshot{Purpose: workerPoolPurposeQueueObservation}
	if t == nil {
		return result
	}
	t.mu.RLock()
	defer t.mu.RUnlock()
	result.Active = t.active
	result.OpenAttempts = t.openAttempts
	result.OpenFailures = t.openFailures
	result.Closes = t.closes
	if t.db != nil {
		result.Stats = t.db.Stats()
	}
	return result
}

type workerDatabaseObservations struct {
	queueObservation *workerAuxiliaryPoolTracker
	rejectedSave     func() auxiliaryDatabasePoolSnapshot
	objectIDProbe    func() transientDatabaseSnapshot
}

func (o workerDatabaseObservations) poolSnapshots() []auxiliaryDatabasePoolSnapshot {
	queue := o.queueObservation.snapshot()
	rejected := auxiliaryDatabasePoolSnapshot{Purpose: workerPoolPurposeRejectedSave}
	if o.rejectedSave != nil {
		rejected = o.rejectedSave()
	}
	return []auxiliaryDatabasePoolSnapshot{queue, rejected}
}

func (o workerDatabaseObservations) transientSnapshots() []transientDatabaseSnapshot {
	result := transientDatabaseSnapshot{Purpose: workerTransientPurposeObjectID}
	if o.objectIDProbe != nil {
		result = o.objectIDProbe()
	}
	return []transientDatabaseSnapshot{result}
}

func openWorkerObservationPool(
	opener func(database.PoolOptions) (*sql.DB, error),
	tracker *workerAuxiliaryPoolTracker,
) (*sql.DB, func() error, error) {
	if tracker == nil {
		db, err := opener(runtimeWorkerObservationPool())
		if err != nil {
			return nil, nil, err
		}
		return db, db.Close, nil
	}
	return tracker.open(opener, runtimeWorkerObservationPool())
}

func optionalWorkerAuxiliaryPoolTracker(trackers []*workerAuxiliaryPoolTracker) *workerAuxiliaryPoolTracker {
	if len(trackers) == 0 {
		return nil
	}
	return trackers[0]
}
