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

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

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

const (
	Name = "mscluster"

	subCollectorCluster       = "cluster"
	subCollectorNetwork       = "network"
	subCollectorNode          = "node"
	subCollectorResource      = "resource"
	subCollectorResourceGroup = "resourcegroup"
	subCollectorSharedVolumes = "shared_volumes"
	subCollectorVirtualDisk   = "virtualdisk"
)

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

//nolint:gochecknoglobals
var ConfigDefaults = Config{
	CollectorsEnabled: []string{
		subCollectorCluster,
		subCollectorNetwork,
		subCollectorNode,
		subCollectorResource,
		subCollectorResourceGroup,
		subCollectorSharedVolumes,
		subCollectorVirtualDisk,
	},
}

// A Collector is a Prometheus Collector for WMI MSCluster_Cluster metrics.
type Collector struct {
	collectorCluster
	collectorNetwork
	collectorNode
	collectorResource
	collectorResourceGroup
	collectorSharedVolumes
	collectorVirtualDisk

	config    Config
	miSession *mi.Session
}

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 collectorsEnabled string

	app.Flag(
		"collector.mscluster.enabled",
		"Comma-separated list of collectors to use.",
	).Default(strings.Join(ConfigDefaults.CollectorsEnabled, ",")).StringVar(&collectorsEnabled)

	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 {
	return nil
}

func (c *Collector) Build(_ *slog.Logger, miSession *mi.Session) error {
	if len(c.config.CollectorsEnabled) == 0 {
		return nil
	}

	if miSession == nil {
		return errors.New("miSession is nil")
	}

	c.miSession = miSession

	errs := make([]error, 0)

	if slices.Contains(c.config.CollectorsEnabled, subCollectorCluster) {
		if err := c.buildCluster(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build cluster collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorNetwork) {
		if err := c.buildNetwork(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build network collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorNode) {
		if err := c.buildNode(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build node collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorResource) {
		if err := c.buildResource(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build resource collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorResourceGroup) {
		if err := c.buildResourceGroup(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build resource group collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorSharedVolumes) {
		if err := c.buildSharedVolumes(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build shared_volumes collector: %w", err))
		}
	}

	if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
		if err := c.buildVirtualDisk(); err != nil {
			errs = append(errs, fmt.Errorf("failed to build virtualdisk 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, maxScrapeDuration time.Duration) error {
	if len(c.config.CollectorsEnabled) == 0 {
		return nil
	}

	errCh := make(chan error, 7)

	wg := sync.WaitGroup{}
	wg.Add(7)

	go func() {
		defer wg.Done()

		if slices.Contains(c.config.CollectorsEnabled, subCollectorCluster) {
			if err := c.collectCluster(ch, maxScrapeDuration); err != nil {
				errCh <- fmt.Errorf("failed to collect cluster metrics: %w", err)
			}
		}
	}()

	go func() {
		defer wg.Done()

		if slices.Contains(c.config.CollectorsEnabled, subCollectorNetwork) {
			if err := c.collectNetwork(ch, maxScrapeDuration); err != nil {
				errCh <- fmt.Errorf("failed to collect network metrics: %w", err)
			}
		}
	}()

	go func() {
		defer wg.Done()

		nodeNames := make([]string, 0)

		if slices.Contains(c.config.CollectorsEnabled, subCollectorNode) {
			var err error

			nodeNames, err = c.collectNode(ch, maxScrapeDuration)
			if err != nil {
				errCh <- fmt.Errorf("failed to collect node metrics: %w", err)
			}
		}

		go func() {
			defer wg.Done()

			if slices.Contains(c.config.CollectorsEnabled, subCollectorResource) {
				if err := c.collectResource(ch, maxScrapeDuration, nodeNames); err != nil {
					errCh <- fmt.Errorf("failed to collect resource metrics: %w", err)
				}
			}
		}()

		go func() {
			defer wg.Done()

			if slices.Contains(c.config.CollectorsEnabled, subCollectorResourceGroup) {
				if err := c.collectResourceGroup(ch, maxScrapeDuration, nodeNames); err != nil {
					errCh <- fmt.Errorf("failed to collect resource group metrics: %w", err)
				}
			}
		}()
	}()

	go func() {
		defer wg.Done()

		if slices.Contains(c.config.CollectorsEnabled, subCollectorSharedVolumes) {
			if err := c.collectSharedVolumes(ch, maxScrapeDuration); err != nil {
				errCh <- fmt.Errorf("failed to collect shared_volumes metrics: %w", err)
			}
		}
	}()

	go func() {
		defer wg.Done()

		if slices.Contains(c.config.CollectorsEnabled, subCollectorVirtualDisk) {
			if err := c.collectVirtualDisk(ch, maxScrapeDuration); err != nil {
				errCh <- fmt.Errorf("failed to collect virtualdisk metrics: %w", err)
			}
		}
	}()

	wg.Wait()
	close(errCh)

	errs := make([]error, 0)

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

	return errors.Join(errs...)
}
