package services

import (
	"testing"
	"time"
)

func TestMonthsBetween(t *testing.T) {
	tests := []struct {
		name     string
		start    time.Time
		end      time.Time
		expected int
	}{
		{
			name:     "Same day",
			start:    time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			expected: 0,
		},
		{
			name:     "Less than one month",
			start:    time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 2, 10, 0, 0, 0, 0, time.UTC),
			expected: 0,
		},
		{
			name:     "Exactly one month",
			start:    time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC),
			expected: 1,
		},
		{
			name:     "One month and one day",
			start:    time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 2, 16, 0, 0, 0, 0, time.UTC),
			expected: 1,
		},
		{
			name:     "Two months",
			start:    time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 3, 15, 0, 0, 0, 0, time.UTC),
			expected: 2,
		},
		{
			name:     "Five months exactly",
			start:    time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 6, 1, 0, 0, 0, 0, time.UTC),
			expected: 5,
		},
		{
			name:     "Year boundary",
			start:    time.Date(2024, 11, 15, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC),
			expected: 3,
		},
		{
			name:     "Leap year February to March",
			start:    time.Date(2024, 2, 29, 0, 0, 0, 0, time.UTC),
			end:      time.Date(2024, 3, 29, 0, 0, 0, 0, time.UTC),
			expected: 1,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := monthsBetween(tt.start, tt.end)
			if result != tt.expected {
				t.Errorf("monthsBetween(%v, %v) = %d, expected %d",
					tt.start.Format("2006-01-02"), tt.end.Format("2006-01-02"),
					result, tt.expected)
			}
		})
	}
}

func TestCarryoverLogic(t *testing.T) {
	// Test the carryover calculation logic
	tests := []struct {
		name                 string
		currentBalance       int
		monthlyAllowance     int
		maxBalance           int
		monthsSinceRenewal   int
		expectedNewBalance   int
	}{
		{
			name:               "Normal renewal - add allowance",
			currentBalance:     50,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 1,
			expectedNewBalance: 80,
		},
		{
			name:               "Two months missed",
			currentBalance:     50,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 2,
			expectedNewBalance: 110,
		},
		{
			name:               "Cap at max balance",
			currentBalance:     140,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 1,
			expectedNewBalance: 150,
		},
		{
			name:               "Already at max - no change",
			currentBalance:     150,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 1,
			expectedNewBalance: 150,
		},
		{
			name:               "Multiple months - cap applies",
			currentBalance:     100,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 5,
			expectedNewBalance: 150,
		},
		{
			name:               "Empty balance - add one month",
			currentBalance:     0,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 1,
			expectedNewBalance: 30,
		},
		{
			name:               "Empty balance - add five months",
			currentBalance:     0,
			monthlyAllowance:   30,
			maxBalance:         150,
			monthsSinceRenewal: 5,
			expectedNewBalance: 150,
		},
		{
			name:               "Standard plan - normal case",
			currentBalance:     200,
			monthlyAllowance:   100,
			maxBalance:         500,
			monthsSinceRenewal: 1,
			expectedNewBalance: 300,
		},
		{
			name:               "Premium plan - Fair Use",
			currentBalance:     1000,
			monthlyAllowance:   1000,
			maxBalance:         5000,
			monthsSinceRenewal: 1,
			expectedNewBalance: 2000,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Simulate the carryover logic
			newBalance := tt.currentBalance
			for i := 0; i < tt.monthsSinceRenewal; i++ {
				newBalance += tt.monthlyAllowance
				if newBalance > tt.maxBalance {
					newBalance = tt.maxBalance
					break
				}
			}

			if newBalance != tt.expectedNewBalance {
				t.Errorf("Carryover for balance=%d, allowance=%d, max=%d, months=%d = %d, expected %d",
					tt.currentBalance, tt.monthlyAllowance, tt.maxBalance, tt.monthsSinceRenewal,
					newBalance, tt.expectedNewBalance)
			}
		})
	}
}

func TestTaskBalanceAfterConsumption(t *testing.T) {
	tests := []struct {
		name               string
		currentBalance     int
		tasksToConsume     int
		expectedBalance    int
		shouldBeAllowed    bool
	}{
		{
			name:            "Normal consumption",
			currentBalance:  50,
			tasksToConsume:  1,
			expectedBalance: 49,
			shouldBeAllowed: true,
		},
		{
			name:            "Last task",
			currentBalance:  1,
			tasksToConsume:  1,
			expectedBalance: 0,
			shouldBeAllowed: true,
		},
		{
			name:            "Empty balance - not allowed",
			currentBalance:  0,
			tasksToConsume:  1,
			expectedBalance: 0,
			shouldBeAllowed: false,
		},
		{
			name:            "Multiple tasks",
			currentBalance:  50,
			tasksToConsume:  5,
			expectedBalance: 45,
			shouldBeAllowed: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Test if allowed
			allowed := tt.currentBalance > 0
			if allowed != tt.shouldBeAllowed {
				t.Errorf("Task allowed with balance=%d: got %v, expected %v",
					tt.currentBalance, allowed, tt.shouldBeAllowed)
			}

			// Test balance calculation
			if allowed {
				newBalance := tt.currentBalance - tt.tasksToConsume
				if newBalance != tt.expectedBalance {
					t.Errorf("Balance after consuming %d tasks from %d: got %d, expected %d",
						tt.tasksToConsume, tt.currentBalance, newBalance, tt.expectedBalance)
				}
			}
		})
	}
}

func TestTaskServiceErrors(t *testing.T) {
	// Test error constants
	if ErrTaskLimitReached == nil {
		t.Error("ErrTaskLimitReached should not be nil")
	}
	if ErrTaskLimitReached.Error() != "TASK_LIMIT_REACHED" {
		t.Errorf("ErrTaskLimitReached should be 'TASK_LIMIT_REACHED', got '%s'", ErrTaskLimitReached.Error())
	}

	if ErrNoSubscription == nil {
		t.Error("ErrNoSubscription should not be nil")
	}
	if ErrNoSubscription.Error() != "NO_SUBSCRIPTION" {
		t.Errorf("ErrNoSubscription should be 'NO_SUBSCRIPTION', got '%s'", ErrNoSubscription.Error())
	}
}

func TestRenewalDateCalculation(t *testing.T) {
	tests := []struct {
		name               string
		lastRenewal        time.Time
		monthsToAdd        int
		expectedRenewal    time.Time
	}{
		{
			name:            "Add one month",
			lastRenewal:     time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			monthsToAdd:     1,
			expectedRenewal: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC),
		},
		{
			name:            "Add three months",
			lastRenewal:     time.Date(2025, 1, 15, 0, 0, 0, 0, time.UTC),
			monthsToAdd:     3,
			expectedRenewal: time.Date(2025, 4, 15, 0, 0, 0, 0, time.UTC),
		},
		{
			name:            "Year boundary",
			lastRenewal:     time.Date(2024, 11, 15, 0, 0, 0, 0, time.UTC),
			monthsToAdd:     3,
			expectedRenewal: time.Date(2025, 2, 15, 0, 0, 0, 0, time.UTC),
		},
		{
			name:            "End of month adjustment",
			lastRenewal:     time.Date(2025, 1, 31, 0, 0, 0, 0, time.UTC),
			monthsToAdd:     1,
			// Go's AddDate handles this - February doesn't have 31 days
			expectedRenewal: time.Date(2025, 3, 3, 0, 0, 0, 0, time.UTC), // Feb 31 -> March 3
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := tt.lastRenewal.AddDate(0, tt.monthsToAdd, 0)
			if !result.Equal(tt.expectedRenewal) {
				t.Errorf("AddDate(%v, %d months) = %v, expected %v",
					tt.lastRenewal.Format("2006-01-02"), tt.monthsToAdd,
					result.Format("2006-01-02"), tt.expectedRenewal.Format("2006-01-02"))
			}
		})
	}
}

func TestFairUseModeLogic(t *testing.T) {
	// Test that Fair Use mode always allows tasks regardless of balance
	tests := []struct {
		name         string
		fairUseMode  bool
		balance      int
		shouldAllow  bool
	}{
		{
			name:        "Fair Use - zero balance still allowed",
			fairUseMode: true,
			balance:     0,
			shouldAllow: true,
		},
		{
			name:        "Fair Use - normal balance allowed",
			fairUseMode: true,
			balance:     1000,
			shouldAllow: true,
		},
		{
			name:        "Not Fair Use - zero balance not allowed",
			fairUseMode: false,
			balance:     0,
			shouldAllow: false,
		},
		{
			name:        "Not Fair Use - positive balance allowed",
			fairUseMode: false,
			balance:     50,
			shouldAllow: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			// Simulate the check logic
			var allowed bool
			if tt.fairUseMode {
				allowed = true // Fair Use always allows
			} else {
				allowed = tt.balance > 0
			}

			if allowed != tt.shouldAllow {
				t.Errorf("FairUseMode=%v, balance=%d: allowed=%v, expected=%v",
					tt.fairUseMode, tt.balance, allowed, tt.shouldAllow)
			}
		})
	}
}

func TestBalanceDecrementLogic(t *testing.T) {
	// Test that Fair Use mode doesn't decrement balance
	tests := []struct {
		name           string
		fairUseMode    bool
		initialBalance int
		expectedAfter  int
	}{
		{
			name:           "Normal plan - decrement",
			fairUseMode:    false,
			initialBalance: 50,
			expectedAfter:  49,
		},
		{
			name:           "Fair Use - no decrement",
			fairUseMode:    true,
			initialBalance: 1000,
			expectedAfter:  1000,
		},
		{
			name:           "Normal plan - last task",
			fairUseMode:    false,
			initialBalance: 1,
			expectedAfter:  0,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			newBalance := tt.initialBalance
			if !tt.fairUseMode {
				newBalance = tt.initialBalance - 1
			}

			if newBalance != tt.expectedAfter {
				t.Errorf("FairUseMode=%v, initial=%d: got %d, expected %d",
					tt.fairUseMode, tt.initialBalance, newBalance, tt.expectedAfter)
			}
		})
	}
}
