// 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 nps

import (
	"errors"
	"fmt"
	"log/slog"
	"time"

	"github.com/alecthomas/kingpin/v2"
	"github.com/prometheus-community/windows_exporter/internal/mi"
	"github.com/prometheus-community/windows_exporter/internal/pdh"
	"github.com/prometheus-community/windows_exporter/internal/types"
	"github.com/prometheus/client_golang/prometheus"
)

const Name = "nps"

type Config struct{}

//nolint:gochecknoglobals
var ConfigDefaults = Config{}

type Collector struct {
	config Config

	accessPerfDataCollector *pdh.Collector
	accessPerfDataObject    []perfDataCounterValuesAccess
	accessAccepts           *prometheus.Desc
	accessChallenges        *prometheus.Desc
	accessRejects           *prometheus.Desc
	accessRequests          *prometheus.Desc
	accessBadAuthenticators *prometheus.Desc
	accessDroppedPackets    *prometheus.Desc
	accessInvalidRequests   *prometheus.Desc
	accessMalformedPackets  *prometheus.Desc
	accessPacketsReceived   *prometheus.Desc
	accessPacketsSent       *prometheus.Desc
	accessServerResetTime   *prometheus.Desc
	accessServerUpTime      *prometheus.Desc
	accessUnknownType       *prometheus.Desc

	accountingPerfDataCollector *pdh.Collector
	accountingPerfDataObject    []perfDataCounterValuesAccounting
	accountingRequests          *prometheus.Desc
	accountingResponses         *prometheus.Desc
	accountingBadAuthenticators *prometheus.Desc
	accountingDroppedPackets    *prometheus.Desc
	accountingInvalidRequests   *prometheus.Desc
	accountingMalformedPackets  *prometheus.Desc
	accountingNoRecord          *prometheus.Desc
	accountingPacketsReceived   *prometheus.Desc
	accountingPacketsSent       *prometheus.Desc
	accountingServerResetTime   *prometheus.Desc
	accountingServerUpTime      *prometheus.Desc
	accountingUnknownType       *prometheus.Desc
}

func New(config *Config) *Collector {
	if config == nil {
		config = &ConfigDefaults
	}

	c := &Collector{
		config: *config,
	}

	return c
}

func NewWithFlags(_ *kingpin.Application) *Collector {
	return &Collector{}
}

func (c *Collector) GetName() string {
	return Name
}

func (c *Collector) Close() error {
	return nil
}

func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
	c.accessAccepts = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_accepts"),
		"(AccessAccepts)",
		nil,
		nil,
	)
	c.accessChallenges = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_challenges"),
		"(AccessChallenges)",
		nil,
		nil,
	)
	c.accessRejects = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_rejects"),
		"(AccessRejects)",
		nil,
		nil,
	)
	c.accessRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_requests"),
		"(AccessRequests)",
		nil,
		nil,
	)
	c.accessBadAuthenticators = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_bad_authenticators"),
		"(BadAuthenticators)",
		nil,
		nil,
	)
	c.accessDroppedPackets = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_dropped_packets"),
		"(DroppedPackets)",
		nil,
		nil,
	)
	c.accessInvalidRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_invalid_requests"),
		"(InvalidRequests)",
		nil,
		nil,
	)
	c.accessMalformedPackets = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_malformed_packets"),
		"(MalformedPackets)",
		nil,
		nil,
	)
	c.accessPacketsReceived = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_packets_received"),
		"(PacketsReceived)",
		nil,
		nil,
	)
	c.accessPacketsSent = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_packets_sent"),
		"(PacketsSent)",
		nil,
		nil,
	)
	c.accessServerResetTime = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_server_reset_time"),
		"(ServerResetTime)",
		nil,
		nil,
	)
	c.accessServerUpTime = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_server_up_time"),
		"(ServerUpTime)",
		nil,
		nil,
	)
	c.accessUnknownType = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "access_unknown_type"),
		"(UnknownType)",
		nil,
		nil,
	)

	c.accountingRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_requests"),
		"(AccountingRequests)",
		nil,
		nil,
	)
	c.accountingResponses = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_responses"),
		"(AccountingResponses)",
		nil,
		nil,
	)
	c.accountingBadAuthenticators = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_bad_authenticators"),
		"(BadAuthenticators)",
		nil,
		nil,
	)
	c.accountingDroppedPackets = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_dropped_packets"),
		"(DroppedPackets)",
		nil,
		nil,
	)
	c.accountingInvalidRequests = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_invalid_requests"),
		"(InvalidRequests)",
		nil,
		nil,
	)
	c.accountingMalformedPackets = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_malformed_packets"),
		"(MalformedPackets)",
		nil,
		nil,
	)
	c.accountingNoRecord = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_no_record"),
		"(NoRecord)",
		nil,
		nil,
	)
	c.accountingPacketsReceived = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_packets_received"),
		"(PacketsReceived)",
		nil,
		nil,
	)
	c.accountingPacketsSent = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_packets_sent"),
		"(PacketsSent)",
		nil,
		nil,
	)
	c.accountingServerResetTime = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_server_reset_time"),
		"(ServerResetTime)",
		nil,
		nil,
	)
	c.accountingServerUpTime = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_server_up_time"),
		"(ServerUpTime)",
		nil,
		nil,
	)
	c.accountingUnknownType = prometheus.NewDesc(
		prometheus.BuildFQName(types.Namespace, Name, "accounting_unknown_type"),
		"(UnknownType)",
		nil,
		nil,
	)

	var err error

	errs := make([]error, 0)

	c.accessPerfDataCollector, err = pdh.NewCollector[perfDataCounterValuesAccess](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "NPS Authentication Server", nil)
	if err != nil {
		errs = append(errs, fmt.Errorf("failed to create NPS Authentication Server collector: %w", err))
	}

	c.accountingPerfDataCollector, err = pdh.NewCollector[perfDataCounterValuesAccounting](logger.With(slog.String("collector", Name)), pdh.CounterTypeRaw, "NPS Accounting Server", nil)
	if err != nil {
		errs = append(errs, fmt.Errorf("failed to create NPS Accounting Server collector: %w", err))
	}

	return errors.Join(errs...)
}

// Collect sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error {
	errs := make([]error, 0)

	if err := c.collectAccept(ch); err != nil {
		errs = append(errs, fmt.Errorf("failed collecting NPS accept data: %w", err))
	}

	if err := c.collectAccounting(ch); err != nil {
		errs = append(errs, fmt.Errorf("failed collecting NPS accounting data: %w", err))
	}

	return errors.Join(errs...)
}

// collectAccept sends the metric values for each metric
// to the provided prometheus Metric channel.
func (c *Collector) collectAccept(ch chan<- prometheus.Metric) error {
	err := c.accessPerfDataCollector.Collect(&c.accessPerfDataObject)
	if err != nil {
		return fmt.Errorf("failed to collect NPS Authentication Server metrics: %w", err)
	}

	ch <- prometheus.MustNewConstMetric(
		c.accessAccepts,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessAccepts,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessChallenges,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessChallenges,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessRejects,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessRejects,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessRequests,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessRequests,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessBadAuthenticators,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessBadAuthenticators,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessDroppedPackets,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessDroppedPackets,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessInvalidRequests,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessInvalidRequests,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessMalformedPackets,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessMalformedPackets,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessPacketsReceived,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessPacketsReceived,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessPacketsSent,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessPacketsSent,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessServerResetTime,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessServerResetTime,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessServerUpTime,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessServerUpTime,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accessUnknownType,
		prometheus.CounterValue,
		c.accessPerfDataObject[0].AccessUnknownType,
	)

	return nil
}

func (c *Collector) collectAccounting(ch chan<- prometheus.Metric) error {
	err := c.accountingPerfDataCollector.Collect(&c.accountingPerfDataObject)
	if err != nil {
		return fmt.Errorf("failed to collect NPS Accounting Server metrics: %w", err)
	}

	ch <- prometheus.MustNewConstMetric(
		c.accountingRequests,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingRequests,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingResponses,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingResponses,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingBadAuthenticators,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingBadAuthenticators,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingDroppedPackets,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingDroppedPackets,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingInvalidRequests,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingInvalidRequests,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingMalformedPackets,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingMalformedPackets,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingNoRecord,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingNoRecord,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingPacketsReceived,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingPacketsReceived,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingPacketsSent,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingPacketsSent,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingServerResetTime,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingServerResetTime,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingServerUpTime,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingServerUpTime,
	)

	ch <- prometheus.MustNewConstMetric(
		c.accountingUnknownType,
		prometheus.CounterValue,
		c.accountingPerfDataObject[0].AccountingUnknownType,
	)

	return nil
}
