import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
import { ASPAAdoptionScheduler } from '../scheduler'

vi.mock('node-cron', () => ({
  default: {
    schedule: vi.fn(() => ({
      start: vi.fn(),
      stop: vi.fn(),
      destroy: vi.fn(),
    })),
  },
}))

vi.mock('../sampler', () => ({
  ASNSampler: vi.fn(() => ({
    sample: vi.fn(() => ({
      asns: [13335, 15169, 8452],
      totalSampled: 3,
      timestamp: new Date(),
      weightedByRegion: { APNIC: 2, RIPE: 1 },
    })),
  })),
}))

vi.mock('../collector', () => ({
  ASPACollector: vi.fn(() => ({
    collect: vi.fn().mockResolvedValue([
      { asn: 13335, hasASPA: true, providers: [1, 2], region: 'APNIC' },
      { asn: 15169, hasASPA: false, providers: [], region: 'APNIC' },
      { asn: 8452, hasASPA: true, providers: [100], region: 'RIPE' },
    ]),
  })),
}))

vi.mock('../aggregator', () => ({
  AdoptionAggregator: vi.fn(() => ({
    aggregate: vi.fn(() => ({
      overall: {
        total: 3,
        withASPA: 2,
        coverage: 66.67,
      },
      regions: [
        { region: 'APNIC', totalASNs: 2, ASNsWithASPA: 2, coveragePercentage: 100 },
        { region: 'RIPE', totalASNs: 1, ASNsWithASPA: 1, coveragePercentage: 100 },
      ],
      ixps: [],
    })),
  })),
}))

vi.mock('../forecaster', () => ({
  AdoptionForecaster: vi.fn(() => ({
    forecast: vi.fn(() => ({
      trend: 'up',
      trendStrength: 0.85,
      confidence: 0.92,
      predictedCoverage6m: 75.5,
    })),
  })),
}))

vi.mock('../db-client', () => ({
  ASPADatabaseClient: vi.fn(() => ({
    recordAdoptionSnapshot: vi.fn().mockResolvedValue({
      id: 1,
      sampleDate: new Date(),
    }),
    recordRegionalBreakdown: vi.fn().mockResolvedValue([]),
    recordIXPBreakdown: vi.fn().mockResolvedValue([]),
    getAdoptionHistory: vi.fn().mockResolvedValue(
      Array.from({ length: 30 }, (_, i) => ({
        sampleDate: new Date(Date.now() - (30 - i) * 24 * 60 * 60 * 1000),
        coveragePercentage: 40 + i * 0.5,
        totalASNsSampled: 500,
        ASNsWithASPA: Math.floor((40 + i * 0.5) * 5),
      }))
    ),
    getRegionalHistory: vi.fn().mockResolvedValue([]),
    getIXPHistory: vi.fn().mockResolvedValue([]),
    getLatestSnapshot: vi.fn().mockResolvedValue(null),
    getStatistics: vi.fn().mockResolvedValue({}),
  })),
}))

describe('ASPAAdoptionScheduler', () => {
  let scheduler: ASPAAdoptionScheduler

  beforeEach(() => {
    scheduler = new ASPAAdoptionScheduler()
    vi.clearAllMocks()
  })

  afterEach(() => {
    scheduler.stop()
    vi.restoreAllMocks()
  })

  describe('scheduler lifecycle', () => {
    it('should initialize without errors', () => {
      expect(scheduler).toBeDefined()
      expect(scheduler.isScheduled()).toBe(false)
      expect(scheduler.isJobRunning()).toBe(false)
    })

    it('should start scheduler successfully', () => {
      scheduler.start()
      expect(scheduler.isScheduled()).toBe(true)
    })

    it('should stop scheduler successfully', () => {
      scheduler.start()
      expect(scheduler.isScheduled()).toBe(true)

      scheduler.stop()
      expect(scheduler.isScheduled()).toBe(false)
    })

    it('should prevent starting scheduler twice', () => {
      scheduler.start()
      expect(scheduler.isScheduled()).toBe(true)

      // Starting again should not create a new task
      scheduler.start()
      expect(scheduler.isScheduled()).toBe(true)
    })

    it('should allow manual job run', async () => {
      await scheduler.runManual()
      expect(scheduler.isJobRunning()).toBe(false) // Should complete
    })

    it('should prevent concurrent job runs', async () => {
      // Set the isRunning flag to simulate a running job
      const promise1 = scheduler.runManual()

      // Try to run again while first is still running
      let errorThrown = false
      try {
        await scheduler.runManual()
      } catch (error: unknown) {
        errorThrown = true
        expect(error instanceof Error && error.message).toContain('already running')
      }

      await promise1
      expect(errorThrown).toBe(true)
    })

    it('should indicate job running state correctly', async () => {
      expect(scheduler.isJobRunning()).toBe(false)

      const runPromise = scheduler.runManual()
      // Job should be marked as running (or completing quickly)

      await runPromise
      expect(scheduler.isJobRunning()).toBe(false)
    })
  })

  describe('job execution', () => {
    it('should execute daily job with all components', async () => {
      await scheduler.runManual()
      // If no errors thrown, job executed successfully
      expect(true).toBe(true)
    })
  })
})
