package httpapi

import (
	"context"
	"fmt"

	"github.com/dtoro/oikos/internal/core/domain"
	"github.com/dtoro/oikos/internal/httpapi/gen"
	"github.com/jackc/pgx/v5"
)

func (s *Server) ListSignals(ctx context.Context, req gen.ListSignalsRequestObject) (gen.ListSignalsResponseObject, error) {
	limit := clampLimit(req.Params.Limit)
	rows, err := s.pool.Query(ctx, `
		SELECT sig.entity_id, se.slug, sig.kind, sig.severity, sig.state,
		       te.slug, sig.check_id::text, sig.evidence, sig.likely_cause,
		       sig.occurrence_count, sig.flap_count, sig.hold_down_until,
		       sig.mute_until, sig.first_seen_at, sig.last_seen_at
		FROM signals sig
		JOIN entities se ON se.id = sig.entity_id
		LEFT JOIN entities te ON te.id = sig.target_entity_id
		WHERE ($1::text IS NULL OR sig.state = $1)
		  AND ($2::text IS NULL OR sig.severity = $2)
		  AND ($3::text IS NULL OR te.slug = $3)
		  AND ($4::text IS NULL OR sig.kind = $4)
		  AND ($5::text IS NULL OR se.slug > $5)
		ORDER BY se.slug
		LIMIT $6`,
		req.Params.State, (*string)(req.Params.Severity), req.Params.EntityId,
		req.Params.Kind, req.Params.Cursor, limit+1)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	items := []gen.Signal{}
	for rows.Next() {
		var sig gen.Signal
		var flap int
		if err := rows.Scan(&sig.Id, &sig.Slug, &sig.Kind, &sig.Severity, &sig.State,
			&sig.Target, &sig.CheckId, &sig.Evidence, &sig.LikelyCause,
			&sig.OccurrenceCount, &flap, &sig.HoldDownUntil,
			&sig.MuteUntil, &sig.FirstSeenAt, &sig.LastSeenAt); err != nil {
			return nil, err
		}
		sig.FlapCount = &flap
		items = append(items, sig)
	}
	if rows.Err() != nil {
		return nil, rows.Err()
	}

	var next *string
	if len(items) > limit {
		items = items[:limit]
		next = &items[len(items)-1].Slug
	}
	return gen.ListSignals200JSONResponse{Items: items, NextCursor: next}, nil
}

func (s *Server) AckSignal(ctx context.Context, req gen.AckSignalRequestObject) (gen.AckSignalResponseObject, error) {
	id, err := s.resolveEntityID(ctx, req.Id)
	if err != nil {
		return nil, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	var sig gen.Signal
	err = tx.QueryRow(ctx, `
		UPDATE signals SET state = 'acknowledged', updated_at = now()
		WHERE entity_id = $1 AND state IN ('raised','failed')
		RETURNING entity_id, (SELECT slug FROM entities WHERE id = $1),
		          kind, severity, 'acknowledged',
		          (SELECT slug FROM entities WHERE id = target_entity_id),
		          check_id::text, evidence, likely_cause,
		          occurrence_count, flap_count, hold_down_until,
		          mute_until, first_seen_at, last_seen_at`,
		id).Scan(&sig.Id, &sig.Slug, &sig.Kind, &sig.Severity, &sig.State,
		&sig.Target, &sig.CheckId, &sig.Evidence, &sig.LikelyCause,
		&sig.OccurrenceCount, &sig.FlapCount, &sig.HoldDownUntil,
		&sig.MuteUntil, &sig.FirstSeenAt, &sig.LastSeenAt)
	if err != nil {
		if err == pgx.ErrNoRows {
			return nil, fmt.Errorf("%w: signal %s not in a state that can be acknowledged", domain.ErrInvalidTransition, req.Id)
		}
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return gen.AckSignal200JSONResponse{SignalUpdatedJSONResponse: gen.SignalUpdatedJSONResponse(sig)}, nil
}

func (s *Server) ResolveSignal(ctx context.Context, req gen.ResolveSignalRequestObject) (gen.ResolveSignalResponseObject, error) {
	id, err := s.resolveEntityID(ctx, req.Id)
	if err != nil {
		return nil, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	var sig gen.Signal
	err = tx.QueryRow(ctx, `
		UPDATE signals SET state = 'resolved', updated_at = now()
		WHERE entity_id = $1 AND state IN ('raised','acknowledged','acting','failed')
		RETURNING entity_id, (SELECT slug FROM entities WHERE id = $1),
		          kind, severity, 'resolved',
		          (SELECT slug FROM entities WHERE id = target_entity_id),
		          check_id::text, evidence, likely_cause,
		          occurrence_count, flap_count, hold_down_until,
		          mute_until, first_seen_at, last_seen_at`,
		id).Scan(&sig.Id, &sig.Slug, &sig.Kind, &sig.Severity, &sig.State,
		&sig.Target, &sig.CheckId, &sig.Evidence, &sig.LikelyCause,
		&sig.OccurrenceCount, &sig.FlapCount, &sig.HoldDownUntil,
		&sig.MuteUntil, &sig.FirstSeenAt, &sig.LastSeenAt)
	if err != nil {
		if err == pgx.ErrNoRows {
			return nil, fmt.Errorf("%w: signal %s not in a state that can be resolved", domain.ErrInvalidTransition, req.Id)
		}
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return gen.ResolveSignal200JSONResponse{SignalUpdatedJSONResponse: gen.SignalUpdatedJSONResponse(sig)}, nil
}

func (s *Server) MuteSignal(ctx context.Context, req gen.MuteSignalRequestObject) (gen.MuteSignalResponseObject, error) {
	id, err := s.resolveEntityID(ctx, req.Id)
	if err != nil {
		return nil, err
	}
	tx, err := s.pool.Begin(ctx)
	if err != nil {
		return nil, err
	}
	defer tx.Rollback(ctx)

	var sig gen.Signal
	err = tx.QueryRow(ctx, `
		UPDATE signals SET state = 'muted', mute_until = $2, updated_at = now()
		WHERE entity_id = $1 AND state IN ('raised','acknowledged')
		RETURNING entity_id, (SELECT slug FROM entities WHERE id = $1),
		          kind, severity, 'muted',
		          (SELECT slug FROM entities WHERE id = target_entity_id),
		          check_id::text, evidence, likely_cause,
		          occurrence_count, flap_count, hold_down_until,
		          mute_until, first_seen_at, last_seen_at`,
		id, req.Body.MuteUntil).Scan(&sig.Id, &sig.Slug, &sig.Kind, &sig.Severity, &sig.State,
		&sig.Target, &sig.CheckId, &sig.Evidence, &sig.LikelyCause,
		&sig.OccurrenceCount, &sig.FlapCount, &sig.HoldDownUntil,
		&sig.MuteUntil, &sig.FirstSeenAt, &sig.LastSeenAt)
	if err != nil {
		if err == pgx.ErrNoRows {
			return nil, fmt.Errorf("%w: signal %s not in a state that can be muted", domain.ErrInvalidTransition, req.Id)
		}
		return nil, err
	}
	if err := tx.Commit(ctx); err != nil {
		return nil, err
	}
	return gen.MuteSignal200JSONResponse{SignalUpdatedJSONResponse: gen.SignalUpdatedJSONResponse(sig)}, nil
}