// SPDX-License-Identifier: Apache-2.0
//
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//go:build windows

package mssql

import (
	"errors"
	"fmt"

	"github.com/prometheus-community/windows_exporter/internal/pdh"
	"github.com/prometheus-community/windows_exporter/internal/types"
	"github.com/prometheus/client_golang/prometheus"
)

type collectorGeneralStatistics struct {
	genStatsPerfDataCollectors map[mssqlInstance]*pdh.Collector
	genStatsPerfDataObject     []perfDataCounterValuesGenStats

	genStatsActiveTempTables              *prometheus.Desc
	genStatsConnectionReset               *prometheus.Desc
	genStatsEventNotificationsDelayedDrop *prometheus.Desc
	genStatsHTTPAuthenticatedRequests     *prometheus.Desc
	genStatsLogicalConnections            *prometheus.Desc
	genStatsLogins                        *prometheus.Desc
	genStatsLogouts                       *prometheus.Desc
	genStatsMarsDeadlocks                 *prometheus.Desc
	genStatsNonAtomicYieldRate            *prometheus.Desc
	genStatsProcessesBlocked              *prometheus.Desc
	genStatsSOAPEmptyRequests             *prometheus.Desc
	genStatsSOAPMethodInvocations         *prometheus.Desc
	genStatsSOAPSessionInitiateRequests   *prometheus.Desc
	genStatsSOAPSessionTerminateRequests  *prometheus.Desc
	genStatsSOAPSQLRequests               *prometheus.Desc
	genStatsSOAPWSDLRequests              *prometheus.Desc
	genStatsSQLTraceIOProviderLockWaits   *prometheus.Desc
	genStatsTempDBRecoveryUnitID          *prometheus.Desc
	genStatsTempDBrowSetID                *prometheus.Desc
	genStatsTempTablesCreationRate        *prometheus.Desc
	genStatsTempTablesForDestruction      *prometheus.Desc
	genStatsTraceEventNotificationQueue   *prometheus.Desc
	genStatsTransactions                  *prometheus.Desc
	genStatsUserConnections               *prometheus.Desc
}

type perfDataCounterValuesGenStats struct {
	GenStatsActiveTempTables              float64 `perfdata:"Active Temp Tables"`
	GenStatsConnectionResetPerSec         float64 `perfdata:"Connection Reset/sec"`
	GenStatsEventNotificationsDelayedDrop float64 `perfdata:"Event Notifications Delayed Drop"`
	GenStatsHTTPAuthenticatedRequests     float64 `perfdata:"HTTP Authenticated Requests"`
	GenStatsLogicalConnections            float64 `perfdata:"Logical Connections"`
	GenStatsLoginsPerSec                  float64 `perfdata:"Logins/sec"`
	GenStatsLogoutsPerSec                 float64 `perfdata:"Logouts/sec"`
	GenStatsMarsDeadlocks                 float64 `perfdata:"Mars Deadlocks"`
	GenStatsNonatomicYieldRate            float64 `perfdata:"Non-atomic yield rate"`
	GenStatsProcessesBlocked              float64 `perfdata:"Processes blocked"`
	GenStatsSOAPEmptyRequests             float64 `perfdata:"SOAP Empty Requests"`
	GenStatsSOAPMethodInvocations         float64 `perfdata:"SOAP Method Invocations"`
	GenStatsSOAPSessionInitiateRequests   float64 `perfdata:"SOAP Session Initiate Requests"`
	GenStatsSOAPSessionTerminateRequests  float64 `perfdata:"SOAP Session Terminate Requests"`
	GenStatsSOAPSQLRequests               float64 `perfdata:"SOAP SQL Requests"`
	GenStatsSOAPWSDLRequests              float64 `perfdata:"SOAP WSDL Requests"`
	GenStatsSQLTraceIOProviderLockWaits   float64 `perfdata:"SQL Trace IO Provider Lock Waits"`
	GenStatsTempdbRecoveryUnitID          float64 `perfdata:"Tempdb recovery unit id"`
	GenStatsTempdbRowsetID                float64 `perfdata:"Tempdb rowset id"`
	GenStatsTempTablesCreationRate        float64 `perfdata:"Temp Tables Creation Rate"`
	GenStatsTempTablesForDestruction      float64 `perfdata:"Temp Tables For Destruction"`
	GenStatsTraceEventNotificationQueue   float64 `perfdata:"Trace Event Notification Queue"`
	GenStatsTransactions                  float64 `perfdata:"Transactions"`
	GenStatsUserConnections               float64 `perfdata:"User Connections"`
}

func (c *Collector) buildGeneralStatistics() error {
	var err error

	c.genStatsPerfDataCollectors = make(map[mssqlInstance]*pdh.Collector, len(c.mssqlInstances))
	errs := make([]error, 0, len(c.mssqlInstances))

	for _, sqlInstance := range c.mssqlInstances {
		c.genStatsPerfDataCollectors[sqlInstance], err = pdh.NewCollector[perfDataCounterValuesGenStats](c.logger, pdh.CounterTypeRaw, c.mssqlGetPerfObjectName(sqlInstance, "General Statistics"), nil)
		if err != nil {
			errs = append(errs, fmt.Errorf("failed to create General Statistics collector for instance %s: %w", sqlInstance.name, err))
		}
	}

	// Win32_PerfRawData_{instance}_SQLServerGeneralStatistics
	c.genStatsActiveTempTables = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_active_temp_tables"),
		"(GeneralStatistics.ActiveTempTables)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsConnectionReset = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_connection_resets"),
		"(GeneralStatistics.ConnectionReset)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsEventNotificationsDelayedDrop = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_event_notifications_delayed_drop"),
		"(GeneralStatistics.EventNotificationsDelayedDrop)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsHTTPAuthenticatedRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_http_authenticated_requests"),
		"(GeneralStatistics.HTTPAuthenticatedRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsLogicalConnections = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_logical_connections"),
		"(GeneralStatistics.LogicalConnections)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsLogins = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_logins"),
		"(GeneralStatistics.Logins)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsLogouts = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_logouts"),
		"(GeneralStatistics.Logouts)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsMarsDeadlocks = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_mars_deadlocks"),
		"(GeneralStatistics.MarsDeadlocks)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsNonAtomicYieldRate = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_non_atomic_yields"),
		"(GeneralStatistics.Nonatomicyields)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsProcessesBlocked = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_blocked_processes"),
		"(GeneralStatistics.Processesblocked)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPEmptyRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_empty_requests"),
		"(GeneralStatistics.SOAPEmptyRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPMethodInvocations = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_method_invocations"),
		"(GeneralStatistics.SOAPMethodInvocations)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPSessionInitiateRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_initiate_requests"),
		"(GeneralStatistics.SOAPSessionInitiateRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPSessionTerminateRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soap_session_terminate_requests"),
		"(GeneralStatistics.SOAPSessionTerminateRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPSQLRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soapsql_requests"),
		"(GeneralStatistics.SOAPSQLRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSOAPWSDLRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_soapwsdl_requests"),
		"(GeneralStatistics.SOAPWSDLRequests)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsSQLTraceIOProviderLockWaits = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_sql_trace_io_provider_lock_waits"),
		"(GeneralStatistics.SQLTraceIOProviderLockWaits)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTempDBRecoveryUnitID = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_recovery_unit_ids_generated"),
		"(GeneralStatistics.Tempdbrecoveryunitid)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTempDBrowSetID = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_tempdb_rowset_ids_generated"),
		"(GeneralStatistics.Tempdbrowsetid)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTempTablesCreationRate = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_temp_tables_creations"),
		"(GeneralStatistics.TempTablesCreations)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTempTablesForDestruction = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_temp_tables_awaiting_destruction"),
		"(GeneralStatistics.TempTablesForDestruction)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTraceEventNotificationQueue = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_trace_event_notification_queue_size"),
		"(GeneralStatistics.TraceEventNotificationQueue)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsTransactions = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_transactions"),
		"(GeneralStatistics.Transactions)",
		[]string{"mssql_instance"},
		nil,
	)
	c.genStatsUserConnections = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "genstats_user_connections"),
		"(GeneralStatistics.UserConnections)",
		[]string{"mssql_instance"},
		nil,
	)

	return errors.Join(errs...)
}

func (c *Collector) collectGeneralStatistics(ch chan<- prometheus.Metric) error {
	return c.collect(ch, subCollectorGeneralStatistics, c.genStatsPerfDataCollectors, c.collectGeneralStatisticsInstance)
}

func (c *Collector) collectGeneralStatisticsInstance(ch chan<- prometheus.Metric, sqlInstance mssqlInstance, perfDataCollector *pdh.Collector) error {
	err := perfDataCollector.Collect(&c.genStatsPerfDataObject)
	if err != nil {
		return fmt.Errorf("failed to collect %s metrics: %w", c.mssqlGetPerfObjectName(sqlInstance, "General Statistics"), err)
	}

	ch <- prometheus.MustNewConstMetric(
		c.genStatsActiveTempTables,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsActiveTempTables,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsConnectionReset,
		prometheus.CounterValue,
		c.genStatsPerfDataObject[0].GenStatsConnectionResetPerSec,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsEventNotificationsDelayedDrop,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsEventNotificationsDelayedDrop,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsHTTPAuthenticatedRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsHTTPAuthenticatedRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsLogicalConnections,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsLogicalConnections,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsLogins,
		prometheus.CounterValue,
		c.genStatsPerfDataObject[0].GenStatsLoginsPerSec,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsLogouts,
		prometheus.CounterValue,
		c.genStatsPerfDataObject[0].GenStatsLogoutsPerSec,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsMarsDeadlocks,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsMarsDeadlocks,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsNonAtomicYieldRate,
		prometheus.CounterValue,
		c.genStatsPerfDataObject[0].GenStatsNonatomicYieldRate,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsProcessesBlocked,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsProcessesBlocked,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPEmptyRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPEmptyRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPMethodInvocations,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPMethodInvocations,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPSessionInitiateRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPSessionInitiateRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPSessionTerminateRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPSessionTerminateRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPSQLRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPSQLRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSOAPWSDLRequests,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSOAPWSDLRequests,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsSQLTraceIOProviderLockWaits,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsSQLTraceIOProviderLockWaits,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTempDBRecoveryUnitID,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsTempdbRecoveryUnitID,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTempDBrowSetID,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsTempdbRowsetID,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTempTablesCreationRate,
		prometheus.CounterValue,
		c.genStatsPerfDataObject[0].GenStatsTempTablesCreationRate,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTempTablesForDestruction,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsTempTablesForDestruction,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTraceEventNotificationQueue,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsTraceEventNotificationQueue,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsTransactions,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsTransactions,
		sqlInstance.name,
	)

	ch <- prometheus.MustNewConstMetric(
		c.genStatsUserConnections,
		prometheus.GaugeValue,
		c.genStatsPerfDataObject[0].GenStatsUserConnections,
		sqlInstance.name,
	)

	return nil
}

func (c *Collector) closeGeneralStatistics() {
	for _, perfDataCollector := range c.genStatsPerfDataCollectors {
		perfDataCollector.Close()
	}
}
