package whatsapp

import (
	"strings"
	"testing"

	"github.com/sirupsen/logrus"
	logrustest "github.com/sirupsen/logrus/hooks/test"
	"github.com/stretchr/testify/assert"
)

// TestParseMaxOpenFilesLimit covers parseMaxOpenFilesLimit (fdmonitor_parse.go),
// which has no build tag specifically so it can run on every platform even
// though its only production caller (readFDStats) is Linux-only.
func TestParseMaxOpenFilesLimit(t *testing.T) {
	tests := []struct {
		name      string
		input     string
		wantLimit int
		wantOK    bool
	}{
		{
			// Soft and hard limits are deliberately distinct here (unlike a
			// prior version of this fixture, which used the same value for
			// both): parseMaxOpenFilesLimit must return the SOFT limit
			// (fields[3]), and a mutation that reads the HARD limit
			// (fields[4]) instead needs a fixture where the two differ to be
			// caught. wantLimit asserts the soft value, 65535, not the hard
			// value, 1048576.
			name:      "normal line",
			input:     "Max open files            65535                1048576              files\n",
			wantLimit: 65535,
			wantOK:    true,
		},
		{
			// Soft "unlimited" (fields[3]) with a distinct, non-unlimited
			// hard value (fields[4]) - same fields[3]-vs-fields[4] mutation
			// coverage as above, but for the "unlimited" branch specifically.
			name:      "unlimited",
			input:     "Max open files            unlimited            1048576              files\n",
			wantLimit: 0,
			wantOK:    true,
		},
		{
			name: "missing Max open files line",
			input: "Max cpu time              unlimited            unlimited            seconds\n" +
				"Max file size             unlimited            unlimited            bytes\n",
			wantLimit: 0,
			wantOK:    false,
		},
		{
			name:      "truncated line",
			input:     "Max open files\n",
			wantLimit: 0,
			wantOK:    false,
		},
		{
			name:      "non-numeric soft value",
			input:     "Max open files            notanumber           65535                files\n",
			wantLimit: 0,
			wantOK:    false,
		},
		{
			name:      "empty input",
			input:     "",
			wantLimit: 0,
			wantOK:    false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			limit, ok := parseMaxOpenFilesLimit(strings.NewReader(tt.input))
			assert.Equal(t, tt.wantOK, ok)
			assert.Equal(t, tt.wantLimit, limit)
		})
	}
}

// TestLogFDStats covers the INFO/WARN/skip branching in logFDStats
// (fdmonitor.go), substituting sm.readFDStats on a SessionManager built just
// for this test so no filesystem access is needed and nothing is shared
// with any other SessionManager's fd-monitor goroutine.
func TestLogFDStats(t *testing.T) {
	tests := []struct {
		name       string
		open       int
		limit      int
		ok         bool
		wantLogged bool
		wantLevel  logrus.Level
	}{
		{
			name:       "below warn ratio logs INFO",
			open:       100,
			limit:      65535,
			ok:         true,
			wantLogged: true,
			wantLevel:  logrus.InfoLevel,
		},
		{
			name:       "at warn ratio logs WARN",
			open:       70,
			limit:      100,
			ok:         true,
			wantLogged: true,
			wantLevel:  logrus.WarnLevel,
		},
		{
			name:       "above warn ratio logs WARN",
			open:       95,
			limit:      100,
			ok:         true,
			wantLogged: true,
			wantLevel:  logrus.WarnLevel,
		},
		{
			name:       "just below warn ratio logs INFO",
			open:       69,
			limit:      100,
			ok:         true,
			wantLogged: true,
			wantLevel:  logrus.InfoLevel,
		},
		{
			name:       "not ok logs nothing",
			open:       0,
			limit:      0,
			ok:         false,
			wantLogged: false,
		},
		{
			name:       "unlimited (limit 0) does not divide by zero or warn",
			open:       999999,
			limit:      0,
			ok:         true,
			wantLogged: true,
			wantLevel:  logrus.InfoLevel,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			open, limit, ok := tt.open, tt.limit, tt.ok

			logger, hook := logrustest.NewNullLogger()
			logger.SetLevel(logrus.DebugLevel)
			sm := &SessionManager{
				logger: logger,
				readFDStats: func() (int, int, bool) {
					return open, limit, ok
				},
			}

			sm.logFDStats()

			entries := hook.AllEntries()
			if !tt.wantLogged {
				assert.Empty(t, entries)
				return
			}

			if assert.Len(t, entries, 1) {
				assert.Equal(t, tt.wantLevel, entries[0].Level)
				assert.Equal(t, tt.open, entries[0].Data["open_fds"])
				assert.Equal(t, tt.limit, entries[0].Data["fd_limit"])
			}
		})
	}
}
