package monitoring

import (
	"context"
	"database/sql"
	"encoding/json"
	"fmt"
	"time"

	"github.com/atlasos/calypso/internal/common/database"
	"github.com/atlasos/calypso/internal/common/logger"
	"github.com/google/uuid"
)

// AlertSeverity represents the severity level of an alert
type AlertSeverity string

const (
	AlertSeverityInfo     AlertSeverity = "info"
	AlertSeverityWarning  AlertSeverity = "warning"
	AlertSeverityCritical AlertSeverity = "critical"
)

// AlertSource represents where the alert originated
type AlertSource string

const (
	AlertSourceSystem   AlertSource = "system"
	AlertSourceStorage  AlertSource = "storage"
	AlertSourceSCST     AlertSource = "scst"
	AlertSourceTape     AlertSource = "tape"
	AlertSourceVTL      AlertSource = "vtl"
	AlertSourceTask     AlertSource = "task"
	AlertSourceAPI      AlertSource = "api"
)

// Alert represents a system alert
type Alert struct {
	ID             string                 `json:"id"`
	Severity       AlertSeverity          `json:"severity"`
	Source         AlertSource            `json:"source"`
	Title          string                 `json:"title"`
	Message        string                 `json:"message"`
	ResourceType   string                 `json:"resource_type,omitempty"`
	ResourceID     string                 `json:"resource_id,omitempty"`
	IsAcknowledged bool                   `json:"is_acknowledged"`
	AcknowledgedBy string                 `json:"acknowledged_by,omitempty"`
	AcknowledgedAt *time.Time             `json:"acknowledged_at,omitempty"`
	ResolvedAt     *time.Time             `json:"resolved_at,omitempty"`
	CreatedAt      time.Time              `json:"created_at"`
	Metadata       map[string]interface{} `json:"metadata,omitempty"`
}

// AlertService manages alerts
type AlertService struct {
	db        *database.DB
	logger    *logger.Logger
	eventHub  *EventHub
}

// NewAlertService creates a new alert service
func NewAlertService(db *database.DB, log *logger.Logger) *AlertService {
	return &AlertService{
		db:     db,
		logger: log,
	}
}

// SetEventHub sets the event hub for broadcasting alerts
func (s *AlertService) SetEventHub(eventHub *EventHub) {
	s.eventHub = eventHub
}

// CreateAlert creates a new alert
func (s *AlertService) CreateAlert(ctx context.Context, alert *Alert) error {
	alert.ID = uuid.New().String()
	alert.CreatedAt = time.Now()

	var metadataJSON *string
	if alert.Metadata != nil {
		bytes, err := json.Marshal(alert.Metadata)
		if err != nil {
			return fmt.Errorf("failed to marshal metadata: %w", err)
		}
		jsonStr := string(bytes)
		metadataJSON = &jsonStr
	}

	query := `
		INSERT INTO alerts (id, severity, source, title, message, resource_type, resource_id, metadata)
		VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
	`

	_, err := s.db.ExecContext(ctx, query,
		alert.ID,
		string(alert.Severity),
		string(alert.Source),
		alert.Title,
		alert.Message,
		alert.ResourceType,
		alert.ResourceID,
		metadataJSON,
	)
	if err != nil {
		return fmt.Errorf("failed to create alert: %w", err)
	}

	s.logger.Info("Alert created",
		"alert_id", alert.ID,
		"severity", alert.Severity,
		"source", alert.Source,
		"title", alert.Title,
	)

	// Broadcast alert via WebSocket if event hub is set
	if s.eventHub != nil {
		s.eventHub.BroadcastAlert(alert)
	}

	return nil
}

// ListAlerts retrieves alerts with optional filters
func (s *AlertService) ListAlerts(ctx context.Context, filters *AlertFilters) ([]*Alert, error) {
	query := `
		SELECT id, severity, source, title, message, resource_type, resource_id,
		       is_acknowledged, acknowledged_by, acknowledged_at, resolved_at,
		       created_at, metadata
		FROM alerts
		WHERE 1=1
	`
	args := []interface{}{}
	argIndex := 1

	if filters != nil {
		if filters.Severity != "" {
			query += fmt.Sprintf(" AND severity = $%d", argIndex)
			args = append(args, string(filters.Severity))
			argIndex++
		}
		if filters.Source != "" {
			query += fmt.Sprintf(" AND source = $%d", argIndex)
			args = append(args, string(filters.Source))
			argIndex++
		}
		if filters.IsAcknowledged != nil {
			query += fmt.Sprintf(" AND is_acknowledged = $%d", argIndex)
			args = append(args, *filters.IsAcknowledged)
			argIndex++
		}
		if filters.ResourceType != "" {
			query += fmt.Sprintf(" AND resource_type = $%d", argIndex)
			args = append(args, filters.ResourceType)
			argIndex++
		}
		if filters.ResourceID != "" {
			query += fmt.Sprintf(" AND resource_id = $%d", argIndex)
			args = append(args, filters.ResourceID)
			argIndex++
		}
	}

	query += " ORDER BY created_at DESC"

	if filters != nil && filters.Limit > 0 {
		query += fmt.Sprintf(" LIMIT $%d", argIndex)
		args = append(args, filters.Limit)
	}

	rows, err := s.db.QueryContext(ctx, query, args...)
	if err != nil {
		return nil, fmt.Errorf("failed to query alerts: %w", err)
	}
	defer rows.Close()

	var alerts []*Alert
	for rows.Next() {
		alert, err := s.scanAlert(rows)
		if err != nil {
			return nil, fmt.Errorf("failed to scan alert: %w", err)
		}
		alerts = append(alerts, alert)
	}

	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("error iterating alerts: %w", err)
	}

	return alerts, nil
}

// GetAlert retrieves a single alert by ID
func (s *AlertService) GetAlert(ctx context.Context, alertID string) (*Alert, error) {
	query := `
		SELECT id, severity, source, title, message, resource_type, resource_id,
		       is_acknowledged, acknowledged_by, acknowledged_at, resolved_at,
		       created_at, metadata
		FROM alerts
		WHERE id = $1
	`

	row := s.db.QueryRowContext(ctx, query, alertID)
	alert, err := s.scanAlertRow(row)
	if err != nil {
		if err == sql.ErrNoRows {
			return nil, fmt.Errorf("alert not found")
		}
		return nil, fmt.Errorf("failed to get alert: %w", err)
	}

	return alert, nil
}

// AcknowledgeAlert marks an alert as acknowledged
func (s *AlertService) AcknowledgeAlert(ctx context.Context, alertID string, userID string) error {
	query := `
		UPDATE alerts
		SET is_acknowledged = true, acknowledged_by = $1, acknowledged_at = NOW()
		WHERE id = $2 AND is_acknowledged = false
	`

	result, err := s.db.ExecContext(ctx, query, userID, alertID)
	if err != nil {
		return fmt.Errorf("failed to acknowledge alert: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rows == 0 {
		return fmt.Errorf("alert not found or already acknowledged")
	}

	s.logger.Info("Alert acknowledged", "alert_id", alertID, "user_id", userID)
	return nil
}

// ResolveAlert marks an alert as resolved
func (s *AlertService) ResolveAlert(ctx context.Context, alertID string) error {
	query := `
		UPDATE alerts
		SET resolved_at = NOW()
		WHERE id = $1 AND resolved_at IS NULL
	`

	result, err := s.db.ExecContext(ctx, query, alertID)
	if err != nil {
		return fmt.Errorf("failed to resolve alert: %w", err)
	}

	rows, err := result.RowsAffected()
	if err != nil {
		return fmt.Errorf("failed to get rows affected: %w", err)
	}

	if rows == 0 {
		return fmt.Errorf("alert not found or already resolved")
	}

	s.logger.Info("Alert resolved", "alert_id", alertID)
	return nil
}

// DeleteAlert deletes an alert (soft delete by resolving it)
func (s *AlertService) DeleteAlert(ctx context.Context, alertID string) error {
	// For safety, we'll just resolve it instead of hard delete
	return s.ResolveAlert(ctx, alertID)
}

// AlertFilters represents filters for listing alerts
type AlertFilters struct {
	Severity       AlertSeverity
	Source         AlertSource
	IsAcknowledged *bool
	ResourceType   string
	ResourceID     string
	Limit          int
}

// scanAlert scans a row into an Alert struct
func (s *AlertService) scanAlert(rows *sql.Rows) (*Alert, error) {
	var alert Alert
	var severity, source string
	var resourceType, resourceID, acknowledgedBy sql.NullString
	var acknowledgedAt, resolvedAt sql.NullTime
	var metadata sql.NullString

	err := rows.Scan(
		&alert.ID,
		&severity,
		&source,
		&alert.Title,
		&alert.Message,
		&resourceType,
		&resourceID,
		&alert.IsAcknowledged,
		&acknowledgedBy,
		&acknowledgedAt,
		&resolvedAt,
		&alert.CreatedAt,
		&metadata,
	)
	if err != nil {
		return nil, err
	}

	alert.Severity = AlertSeverity(severity)
	alert.Source = AlertSource(source)
	if resourceType.Valid {
		alert.ResourceType = resourceType.String
	}
	if resourceID.Valid {
		alert.ResourceID = resourceID.String
	}
	if acknowledgedBy.Valid {
		alert.AcknowledgedBy = acknowledgedBy.String
	}
	if acknowledgedAt.Valid {
		alert.AcknowledgedAt = &acknowledgedAt.Time
	}
	if resolvedAt.Valid {
		alert.ResolvedAt = &resolvedAt.Time
	}
	if metadata.Valid && metadata.String != "" {
		json.Unmarshal([]byte(metadata.String), &alert.Metadata)
	}

	return &alert, nil
}

// scanAlertRow scans a single row into an Alert struct
func (s *AlertService) scanAlertRow(row *sql.Row) (*Alert, error) {
	var alert Alert
	var severity, source string
	var resourceType, resourceID, acknowledgedBy sql.NullString
	var acknowledgedAt, resolvedAt sql.NullTime
	var metadata sql.NullString

	err := row.Scan(
		&alert.ID,
		&severity,
		&source,
		&alert.Title,
		&alert.Message,
		&resourceType,
		&resourceID,
		&alert.IsAcknowledged,
		&acknowledgedBy,
		&acknowledgedAt,
		&resolvedAt,
		&alert.CreatedAt,
		&metadata,
	)
	if err != nil {
		return nil, err
	}

	alert.Severity = AlertSeverity(severity)
	alert.Source = AlertSource(source)
	if resourceType.Valid {
		alert.ResourceType = resourceType.String
	}
	if resourceID.Valid {
		alert.ResourceID = resourceID.String
	}
	if acknowledgedBy.Valid {
		alert.AcknowledgedBy = acknowledgedBy.String
	}
	if acknowledgedAt.Valid {
		alert.AcknowledgedAt = &acknowledgedAt.Time
	}
	if resolvedAt.Valid {
		alert.ResolvedAt = &resolvedAt.Time
	}
	if metadata.Valid && metadata.String != "" {
		json.Unmarshal([]byte(metadata.String), &alert.Metadata)
	}

	return &alert, nil
}

