/*
SPDX-FileCopyrightText: Red Hat

SPDX-License-Identifier: Apache-2.0
*/

package listener

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

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
	"sigs.k8s.io/controller-runtime/pkg/client"

	svccommon "github.com/openshift-kni/oran-o2ims/internal/service/common"
	common "github.com/openshift-kni/oran-o2ims/internal/service/common/api/generated"
	"github.com/openshift-kni/oran-o2ims/internal/service/common/clients/k8s"
	svcutils "github.com/openshift-kni/oran-o2ims/internal/service/common/utils"
	"github.com/openshift-kni/oran-o2ims/internal/service/resources/db/models"
	"github.com/openshift-kni/oran-o2ims/internal/service/resources/db/repo"
)

const (
	// AlarmDictionarySchemaVersion is the schema version for alarm dictionaries
	AlarmDictionarySchemaVersion = "1.0.0"
)

// syncAlarmDictionaryForResourceType syncs the alarm dictionary for a specific resource type
func syncAlarmDictionaryForResourceType(ctx context.Context, repository *repo.ResourcesRepository, resourceTypeID uuid.UUID) error {
	slog.InfoContext(ctx, "Syncing alarm dictionary for resource type", slog.String("resource_type_id", resourceTypeID.String()))

	// Get the resource type
	resourceType, err := repository.GetResourceType(ctx, resourceTypeID)
	if err != nil {
		if errors.Is(err, svcutils.ErrNotFound) {
			// Resource type was deleted, alarm dictionary will be cascade deleted
			slog.InfoContext(ctx, "Resource type not found, skipping alarm dictionary sync",
				slog.String("resource_type_id", resourceTypeID.String()))
			return nil
		}
		return fmt.Errorf("failed to get resource type: %w", err)
	}

	// Create K8s client to fetch Prometheus rules
	hubClient, err := k8s.NewClientForHub()
	if err != nil {
		return fmt.Errorf("failed to create k8s client for hub: %w", err)
	}

	// Fetch Ironic Prometheus rules
	promRules, err := getIronicPrometheusRules(ctx, hubClient)
	if err != nil {
		return fmt.Errorf("failed to get Prometheus rules: %w", err)
	}

	// Create alarm dictionary for this resource type
	alarmDict := makeAlarmDictionary(*resourceType)

	// Execute upserts in a transaction to ensure atomicity
	if err := pgx.BeginFunc(ctx, repository.Db, func(tx pgx.Tx) error {
		// Upsert alarm dictionary and get the ID back
		result, err := repository.UpsertAlarmDictionary(ctx, tx, alarmDict)
		if err != nil {
			return fmt.Errorf("failed to upsert alarm dictionary: %w", err)
		}

		// Create alarm definitions using the returned alarm dictionary ID
		alarmDefs := makeAlarmDefinitions(promRules, result.AlarmDictionaryID)

		// Upsert alarm definitions
		if _, err = repository.UpsertAlarmDefinitions(ctx, tx, alarmDefs); err != nil {
			return fmt.Errorf("failed to upsert alarm definitions: %w", err)
		}

		slog.InfoContext(ctx, "Successfully synced alarm dictionary", slog.String("resource_type_id",
			resourceTypeID.String()), slog.String("alarm_dict_id", result.AlarmDictionaryID.String()), slog.Int("definitions_count", len(alarmDefs)))
		return nil
	}); err != nil {
		return fmt.Errorf("transaction failed: %w", err)
	}

	return nil
}

// getIronicPrometheusRules fetches Prometheus rules for hardware monitoring
// Scans all PrometheusRules and filters individual alert rules by the Ironic component label.
func getIronicPrometheusRules(ctx context.Context, cl client.Client) ([]monitoringv1.Rule, error) {
	var promRuleList monitoringv1.PrometheusRuleList

	// List all PrometheusRules (we'll filter at rule level, not CR level)
	if err := cl.List(ctx, &promRuleList); err != nil {
		return nil, fmt.Errorf("error listing PrometheusRules: %w", err)
	}

	// Extract alerting rules from hardware monitoring groups (group-level labels)
	var rules []monitoringv1.Rule
	for _, promRule := range promRuleList.Items {
		for _, group := range promRule.Spec.Groups {
			// Check if this is a hardware monitoring group
			if group.Labels[svccommon.HardwareAlertTypeLabel] == svccommon.HardwareAlertTypeValue &&
				group.Labels[svccommon.HardwareAlertComponentLabel] == svccommon.HardwareAlertComponentValue {
				// Collect all alerting rules from this group
				for _, rule := range group.Rules {
					if rule.Alert != "" {
						rules = append(rules, rule)
					}
				}
			}
		}
	}

	slog.InfoContext(ctx, "Fetched hardware monitoring Prometheus rules", slog.Int("count", len(rules)))
	return rules, nil
}

// makeAlarmDictionary creates an AlarmDictionary from a ResourceType
// The AlarmDictionaryID will be generated by the database
func makeAlarmDictionary(resourceType models.ResourceType) models.AlarmDictionary {
	return models.AlarmDictionary{
		AlarmDictionaryVersion:       resourceType.Version,
		AlarmDictionarySchemaVersion: AlarmDictionarySchemaVersion,
		EntityType:                   fmt.Sprintf("%s-%s", resourceType.Model, resourceType.Version),
		Vendor:                       resourceType.Vendor,
		ManagementInterfaceID:        []string{string(common.AlarmDefinitionManagementInterfaceIdO2IMS)},
		PKNotificationField:          []string{"alarmDefinitionID"},
		ResourceTypeID:               resourceType.ResourceTypeID,
	}
}

// makeAlarmDefinitions creates AlarmDefinitions from Prometheus rules
func makeAlarmDefinitions(rules []monitoringv1.Rule, alarmDictionaryID uuid.UUID) []models.AlarmDefinition {
	var definitions []models.AlarmDefinition

	for _, rule := range rules {
		// Extract additional fields
		additionalFields := map[string]interface{}{
			"Expr": rule.Expr.String(),
		}
		if rule.For != nil {
			additionalFields["For"] = string(*rule.For)
		}
		if rule.KeepFiringFor != nil {
			additionalFields["KeepFiringFor"] = string(*rule.KeepFiringFor)
		}

		// Add severity to additional fields
		severity := rule.Labels["severity"]
		additionalFields["severity"] = severity

		// Extract annotations
		summary := rule.Annotations["summary"]
		description := rule.Annotations["description"]
		runbookURL := rule.Annotations["runbook_url"]

		definition := models.AlarmDefinition{
			AlarmName:             rule.Alert,
			AlarmLastChange:       "", // Will be set by version tracking
			AlarmChangeType:       string(common.ADDED),
			AlarmDescription:      fmt.Sprintf("Summary: %s\nDescription: %s", summary, description),
			ProposedRepairActions: runbookURL,
			ClearingType:          string(common.AUTOMATIC),
			ManagementInterfaceID: []string{string(common.AlarmDefinitionManagementInterfaceIdO2IMS)},
			PKNotificationField:   []string{"alarmDefinitionID"},
			AlarmAdditionalFields: &additionalFields,
			Severity:              severity,
			AlarmDictionaryID:     &alarmDictionaryID,
		}

		definitions = append(definitions, definition)
	}

	return definitions
}
