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

import (
	"errors"
	"fmt"
	"log/slog"
	"os"
	"strings"
	"sync"
	"time"

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

const Name = "exchange"

const (
	subCollectorADAccessProcesses   = "ADAccessProcesses"
	subCollectorTransportQueues     = "TransportQueues"
	subCollectorHttpProxy           = "HttpProxy"
	subCollectorActiveSync          = "ActiveSync"
	subCollectorAvailabilityService = "AvailabilityService"
	subCollectorOutlookWebAccess    = "OutlookWebAccess"
	subCollectorAutoDiscover        = "Autodiscover"
	subCollectorWorkloadManagement  = "WorkloadManagement"
	subCollectorRpcClientAccess     = "RpcClientAccess"
	subCollectorMapiHTTPEmsmdb      = "MapiHttpEmsmdb"
)

type Config struct {
	CollectorsEnabled []string `yaml:"enabled"`
}

//nolint:gochecknoglobals
var ConfigDefaults = Config{
	CollectorsEnabled: []string{
		subCollectorADAccessProcesses,
		subCollectorTransportQueues,
		subCollectorHttpProxy,
		subCollectorActiveSync,
		subCollectorAvailabilityService,
		subCollectorOutlookWebAccess,
		subCollectorAutoDiscover,
		subCollectorWorkloadManagement,
		subCollectorRpcClientAccess,
		subCollectorMapiHTTPEmsmdb,
	},
}

type Collector struct {
	collectorADAccessProcesses
	collectorActiveSync
	collectorAutoDiscover
	collectorAvailabilityService
	collectorHTTPProxy
	collectorMapiHTTPEmsMDB
	collectorOWA
	collectorRpcClientAccess
	collectorTransportQueues
	collectorWorkloadManagementWorkloads

	config Config
	logger *slog.Logger

	collectorFns []func(ch chan<- prometheus.Metric) error
	closeFns     []func()
}

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

	if config.CollectorsEnabled == nil {
		config.CollectorsEnabled = ConfigDefaults.CollectorsEnabled
	}

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

	return c
}

func NewWithFlags(app *kingpin.Application) *Collector {
	c := &Collector{
		config: ConfigDefaults,
	}
	c.config.CollectorsEnabled = make([]string, 0)

	var listAllCollectors bool

	var collectorsEnabled string

	app.Flag(
		"collector.exchange.list",
		"List the collectors along with their perflib object name/ids",
	).BoolVar(&listAllCollectors)

	app.Flag(
		"collector.exchange.enabled",
		"Comma-separated list of collectors to use. Defaults to all, if not specified.",
	).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled)

	app.PreAction(func(*kingpin.ParseContext) error {
		if listAllCollectors {
			collectorDesc := map[string]string{
				subCollectorADAccessProcesses:   "[19108] MSExchange ADAccess Processes",
				subCollectorTransportQueues:     "[20524] MSExchangeTransport Queues",
				subCollectorHttpProxy:           "[36934] MSExchange HttpProxy",
				subCollectorActiveSync:          "[25138] MSExchange ActiveSync",
				subCollectorAvailabilityService: "[24914] MSExchange Availability Service",
				subCollectorOutlookWebAccess:    "[24618] MSExchange OWA",
				subCollectorAutoDiscover:        "[29240] MSExchange Autodiscover",
				subCollectorWorkloadManagement:  "[19430] MSExchange WorkloadManagement Workloads",
				subCollectorRpcClientAccess:     "[29336] MSExchange RpcClientAccess",
				subCollectorMapiHTTPEmsmdb:      "[26463] MSExchange MapiHttp Emsmdb",
			}

			sb := strings.Builder{}
			_, _ = fmt.Fprintf(&sb, "%-32s %-32s\n", "Collector Name", "[PerfID] Perflib Object")

			for _, cname := range ConfigDefaults.CollectorsEnabled {
				_, _ = fmt.Fprintf(&sb, "%-32s %-32s\n", cname, collectorDesc[cname])
			}

			app.UsageTemplate(sb.String()).Usage(nil)

			os.Exit(0)
		}

		return nil
	})

	app.Action(func(*kingpin.ParseContext) error {
		c.config.CollectorsEnabled = strings.Split(collectorsEnabled, ",")

		return nil
	})

	return c
}

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

func (c *Collector) Close() error {
	for _, fn := range c.closeFns {
		fn()
	}

	return nil
}

func (c *Collector) Build(logger *slog.Logger, _ *mi.Session) error {
	c.logger = logger.With(slog.String("collector", Name))

	subCollectors := map[string]struct {
		build   func() error
		collect func(ch chan<- prometheus.Metric) error
		close   func()
	}{
		subCollectorADAccessProcesses: {
			build:   c.buildADAccessProcesses,
			collect: c.collectADAccessProcesses,
			close:   c.perfDataCollectorADAccessProcesses.Close,
		},
		subCollectorTransportQueues: {
			build:   c.buildTransportQueues,
			collect: c.collectTransportQueues,
			close:   c.perfDataCollectorTransportQueues.Close,
		},
		subCollectorHttpProxy: {
			build:   c.buildHTTPProxy,
			collect: c.collectHTTPProxy,
			close:   c.perfDataCollectorHTTPProxy.Close,
		},
		subCollectorActiveSync: {
			build:   c.buildActiveSync,
			collect: c.collectActiveSync,
			close:   c.perfDataCollectorActiveSync.Close,
		},
		subCollectorAvailabilityService: {
			build:   c.buildAvailabilityService,
			collect: c.collectAvailabilityService,
			close:   c.perfDataCollectorAvailabilityService.Close,
		},
		subCollectorOutlookWebAccess: {
			build:   c.buildOWA,
			collect: c.collectOWA,
			close:   c.perfDataCollectorOWA.Close,
		},
		subCollectorAutoDiscover: {
			build:   c.buildAutoDiscover,
			collect: c.collectAutoDiscover,
			close:   c.perfDataCollectorAutoDiscover.Close,
		},
		subCollectorWorkloadManagement: {
			build:   c.buildWorkloadManagementWorkloads,
			collect: c.collectWorkloadManagementWorkloads,
			close:   c.perfDataCollectorWorkloadManagementWorkloads.Close,
		},
		subCollectorRpcClientAccess: {
			build:   c.buildRpcClientAccess,
			collect: c.collectRpcClientAccess,
			close:   c.perfDataCollectorRpcClientAccess.Close,
		},
		subCollectorMapiHTTPEmsmdb: {
			build:   c.buildMapiHTTPEmsMDB,
			collect: c.collectMapiHTTPEmsMDB,
			close:   c.perfDataCollectorMapiHTTPEmsMDB.Close,
		},
	}

	errs := make([]error, 0, len(c.config.CollectorsEnabled))

	for _, name := range c.config.CollectorsEnabled {
		if _, ok := subCollectors[name]; !ok {
			return fmt.Errorf("unknown collector: %s", name)
		}

		if err := subCollectors[name].build(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build %s collector: %w", name, err))

			continue
		}

		c.collectorFns = append(c.collectorFns, subCollectors[name].collect)
		c.closeFns = append(c.closeFns, subCollectors[name].close)
	}

	return errors.Join(errs...)
}

// Collect collects exchange metrics and sends them to prometheus.
func (c *Collector) Collect(ch chan<- prometheus.Metric, _ time.Duration) error {
	errCh := make(chan error, len(c.collectorFns))
	errs := make([]error, 0, len(c.collectorFns))

	wg := sync.WaitGroup{}

	for _, fn := range c.collectorFns {
		wg.Add(1)

		go func(fn func(ch chan<- prometheus.Metric) error) {
			defer wg.Done()

			if err := fn(ch); err != nil {
				errCh <- err
			}
		}(fn)
	}

	wg.Wait()

	close(errCh)

	for err := range errCh {
		errs = append(errs, err)
	}

	return errors.Join(errs...)
}

// toLabelName converts strings to lowercase and replaces all whitespaces and dots with underscores.
func (c *Collector) toLabelName(name string) string {
	s := strings.ReplaceAll(strings.Join(strings.Fields(strings.ToLower(name)), "_"), ".", "_")
	s = strings.ReplaceAll(s, "__", "_")

	return s
}
