package cron

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/go-redis/redis/v8"

	"trip-planner/internal/cache"
	"trip-planner/internal/yandex"
)

func newMockMonitor(id string, tripCount int, scheduleFunc func(context.Context, string) (int, error)) *StationMonitor {
	rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	yc := yandex.NewClient("test-key")

	monitor := &StationMonitor{
		ID:           id,
		Yandex:       yc,
		Cache:        cache.NewCacheStore(rc),
		ScheduleFunc: scheduleFunc,
	}

	// If no ScheduleFunc provided, set up default that returns tripCount
	if monitor.ScheduleFunc == nil {
		monitor.ScheduleFunc = func(ctx context.Context, stationID string) (int, error) {
			return tripCount, nil
		}
	}

	return monitor
}

const testMonitorID = "test-station"

// TestProcessStation_WithTrips verifies that a station with trips today
// gets status "active" and zero-trip day count resets to 0.
func TestProcessStation_WithTrips(t *testing.T) {
	t.Helper()
	rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	defer rc.Close()

	ctx := context.Background()

	// Flush Redis database for test isolation
	rc.FlushDB(ctx)

	monitor := newMockMonitor(testMonitorID, 2, nil)

	// Process the station - should have trips and status should be active
	err := ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Check that status was set to active
	statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get error: %v", err)
	}
	if string(statusData) != string(StatusActive) {
		t.Errorf("expected status active, got %s", string(statusData))
	}

	// Check that zero days was reset to 0
	zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get zero days error: %v", err)
	}
	var zeroDays int
	_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	if err != nil {
		t.Fatalf("failed to parse zero days: %v", err)
	}
	if zeroDays != 0 {
		t.Errorf("expected zero days 0, got %d", zeroDays)
	}
}

// TestProcessStation_ZeroTrips_IncrementsCount verifies that a station
// with 0 trips increments the zero-trip day count.
func TestProcessStation_ZeroTrips_IncrementsCount(t *testing.T) {
	t.Helper()
	rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	defer rc.Close()

	ctx := context.Background()

	// Flush Redis database for test isolation
	rc.FlushDB(ctx)

	// Monitor with 0 trips (schedule func returns 0)
	monitor := newMockMonitor(testMonitorID, 0, nil)

	// First call: 0 trips, status should remain active (zero days = 1)
	err := ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}

	// Check that zero days was incremented to 1
	statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get error: %v", err)
	}
	if string(statusData) != string(StatusActive) {
		t.Errorf("expected status active after first call, got %s", string(statusData))
	}

	zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get zero days error: %v", err)
	}
	var zeroDays int
	_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	if err != nil {
		t.Fatalf("failed to parse zero days: %v", err)
	}
	if zeroDays != 1 {
		t.Errorf("expected zero days 1 after first call, got %d", zeroDays)
	}
}

// TestProcessStation_ZeroTrips_3Days_Closes verifies that a station
// with 3 consecutive days of zero trips gets status "closed".
func TestProcessStation_ZeroTrips_3Days_Closes(t *testing.T) {
	t.Helper()
	rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	defer rc.Close()

	ctx := context.Background()

	// Flush Redis database for test isolation
	rc.FlushDB(ctx)

	// Monitor with 0 trips each day
	monitor := newMockMonitor(testMonitorID, 0, nil)

	// Day 1: 0 trips - ProcessStation reads 0 (no prior data), increments to 1
	err := ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error day 1: %v", err)
	}
	zeroDaysData, _ := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	var zeroDays int
	fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	// ProcessStation starts at 0 (no prior data), increments to 1
	if zeroDays != 1 {
		t.Errorf("day 1: expected zero days 1, got %d", zeroDays)
	}

	// Day 2: 0 trips - ProcessStation reads 1 (from day 1), increments to 2
	err = ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error day 2: %v", err)
	}
	zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	// ProcessStation incremented from 1 to 2
	if zeroDays != 2 {
		t.Errorf("day 2: expected zero days 2, got %d", zeroDays)
	}

	// Day 3: 0 trips - ProcessStation reads 2 (from day 2), increments to 3, closes station
	err = ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error day 3: %v", err)
	}
	zeroDaysData, _ = monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	// ProcessStation incremented from 2 to 3
	if zeroDays != 3 {
		t.Errorf("day 3: expected zero days 3, got %d", zeroDays)
	}

	// Status should be closed after 3 consecutive days of zero trips
	statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get error: %v", err)
	}
	if string(statusData) != string(StatusClosed) {
		t.Errorf("expected status closed after 3 days, got %s", string(statusData))
	}
}

// TestProcessStation_Reactivation_AfterClosure verifies that a station
// closed due to 3 zero-trip days gets reactivated when trips resume.
func TestProcessStation_Reactivation_AfterClosure(t *testing.T) {
	t.Helper()
	rc := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
	defer rc.Close()

	ctx := context.Background()

	// Flush Redis database for test isolation
	rc.FlushDB(ctx)

	// Monitor that will return 1 trip on reactivation
	monitor := newMockMonitor(testMonitorID, 1, nil)

	// First, close the station by setting zero days to 3 and status to closed
	_ = rc.Set(ctx, "station:zero_days:"+testMonitorID, "3", 24*time.Hour)
	_ = rc.Set(ctx, "station:status:"+testMonitorID, string(StatusClosed), 24*time.Hour)

	// Day 4: trips resume - should reactivate
	err := ProcessStation(ctx, monitor)
	if err != nil {
		t.Fatalf("unexpected error on reactivation: %v", err)
	}

	// Status should be active again
	statusData, err := monitor.Cache.Get(ctx, stationStatusKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get error: %v", err)
	}
	if string(statusData) != string(StatusActive) {
		t.Errorf("expected status active after reactivation, got %s", string(statusData))
	}

	// Zero days should be reset to 0
	zeroDaysData, err := monitor.Cache.Get(ctx, zeroDaysKey(testMonitorID))
	if err != nil {
		t.Fatalf("cache get zero days error: %v", err)
	}
	var zeroDays int
	_, err = fmt.Sscanf(string(zeroDaysData), "%d", &zeroDays)
	if err != nil {
		t.Fatalf("failed to parse zero days: %v", err)
	}
	if zeroDays != 0 {
		t.Errorf("expected zero days 0 after reactivation, got %d", zeroDays)
	}
}
