import { describe, it, expect, vi, beforeEach } from 'vitest'
import { AppError, NotFoundError } from '../../http/errors'

vi.mock('@rentaldrivego/types', () => ({
  PLAN_FEATURES: { STARTER: ['fallback feature'] },
  PLAN_PRICES: { STARTER: { MONTHLY: { MAD: 199 } } },
}))

vi.mock('../../lib/prisma', () => ({
  prisma: {
    pricingConfig: { findMany: vi.fn() },
    planFeature: { findMany: vi.fn() },
  },
}))

vi.mock('./site.repo', () => ({
  findCompanyBySlug:          vi.fn(),
  findPublishedVehicles:      vi.fn(),
  findVehicleById:            vi.fn(),
  findActiveOffers:           vi.fn(),
  findInsurancePolicies:      vi.fn(),
  findOfferByPromoCode:       vi.fn(),
  upsertCustomer:             vi.fn(),
  createReservation:          vi.fn(),
  findReservationWithDetails: vi.fn(),
  findBooking:                vi.fn(),
  findReservationForPayment:  vi.fn(),
  createReservationPublicAccess: vi.fn(),
  findReservationPublicAccess: vi.fn(),
  markReservationPublicAccessUsed: vi.fn(),
  createRentalPayment:        vi.fn(),
  findPaymentByPaypalOrderId: vi.fn(),
  capturePaypalPayment:       vi.fn(),
}))

vi.mock('../../services/vehicleAvailabilityService', () => ({
  getVehicleAvailabilitySummary: vi.fn(),
}))

vi.mock('../../services/insuranceService', () => ({
  applyInsurancesToReservation: vi.fn().mockResolvedValue(undefined),
}))

vi.mock('../../services/additionalDriverService', () => ({
  applyAdditionalDriversToReservation: vi.fn().mockResolvedValue(undefined),
}))

vi.mock('../../services/pricingRuleService', () => ({
  applyPricingRules: vi.fn(),
}))

vi.mock('../../services/licenseValidationService', () => ({
  validateLicense: vi.fn(),
  validateAndFlagLicense: vi.fn().mockResolvedValue(undefined),
}))

vi.mock('../../services/amanpayService', () => ({
  isConfigured:   vi.fn(),
  createCheckout: vi.fn(),
}))

vi.mock('../../services/paypalService', () => ({
  isConfigured: vi.fn(),
  createOrder:  vi.fn(),
  captureOrder: vi.fn(),
}))

vi.mock('../../services/platformContentService', () => ({
  getCarplaceHomepageContent: vi.fn(),
}))

import * as repo from './site.repo'
import { getVehicleAvailabilitySummary } from '../../services/vehicleAvailabilityService'
import { applyPricingRules } from '../../services/pricingRuleService'
import { validateLicense } from '../../services/licenseValidationService'
import * as amanpay from '../../services/amanpayService'
import * as paypalSvc from '../../services/paypalService'
import {
  getBrand, getPublicVehicles, checkAvailability, validatePromoCode,
  createBooking, initPayment,
} from './site.service'

const SLUG = 'test-company'

function futureDateRange(startOffsetDays: number, durationDays: number) {
  const start = new Date(Date.now() + startOffsetDays * 24 * 60 * 60 * 1000)
  const end = new Date(start.getTime() + durationDays * 24 * 60 * 60 * 1000)
  return { start, end }
}

function makeCompany(overrides: object = {}) {
  return {
    id: 'co-1', slug: SLUG, name: 'Test Co', phone: null, email: 'co@test.com',
    brand: { publicEmail: null }, contractSettings: null,
    ...overrides,
  }
}

function makeVehicle(overrides: object = {}) {
  return {
    id: 'v-1', dailyRate: 10000, year: 2022, make: 'Toyota', model: 'Camry',
    isPublished: true, status: 'AVAILABLE', companyId: 'co-1', category: 'SEDAN',
    ...overrides,
  }
}

beforeEach(() => {
  vi.clearAllMocks()
  vi.mocked(repo.createReservationPublicAccess).mockResolvedValue({ id: 'access_1' } as never)
  vi.mocked(repo.findReservationPublicAccess).mockResolvedValue({ id: 'access_1' } as never)
  vi.mocked(repo.markReservationPublicAccessUsed).mockResolvedValue({ id: 'access_1' } as never)
})

// ────────────────────────────────────────────────────────────────────────────
describe('getBrand', () => {
  it('returns company and public brand data only', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany({
      brand: {
        displayName: 'Test Co',
        paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
        amanpayMerchantId: 'merchant-id',
        amanpaySecretKey: 'top-secret',
        paypalMerchantId: 'paypal-merchant-id',
      },
    }) as any)
    const result = await getBrand(SLUG)

    expect(result.company.id).toBe('co-1')
    expect(result.brand).toMatchObject({
      displayName: 'Test Co',
      paymentMethodsEnabled: ['AMANPAY', 'PAYPAL'],
      paypalMerchantId: 'paypal-merchant-id',
    })
    expect(result.brand).not.toHaveProperty('amanpayMerchantId')
    expect(result.brand).not.toHaveProperty('amanpaySecretKey')
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('getPublicVehicles', () => {
  it('returns vehicles enriched with availability', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findPublishedVehicles).mockResolvedValue([makeVehicle()] as any)
    vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })

    const result = await getPublicVehicles(SLUG)
    expect(result[0].availability).toBe(true)
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('checkAvailability', () => {
  it('returns availability result for given date range', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findVehicleById).mockResolvedValue(makeVehicle() as any)
    vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })

    const result = await checkAvailability(SLUG, 'v-1', '2025-06-01T00:00:00.000Z', '2025-06-04T00:00:00.000Z')
    expect(result.available).toBe(true)
    expect(getVehicleAvailabilitySummary).toHaveBeenCalledWith('v-1', { companyId: 'co-1', range: { startDate: new Date('2025-06-01T00:00:00.000Z'), endDate: new Date('2025-06-04T00:00:00.000Z') } })
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('validatePromoCode', () => {
  it('returns offer when code is valid', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findOfferByPromoCode).mockResolvedValue({ id: 'o-1' } as any)
    const result = await validatePromoCode(SLUG, 'SUMMER10')
    expect((result as any).id).toBe('o-1')
  })

  it('throws NotFoundError for unknown promo code', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findOfferByPromoCode).mockResolvedValue(null)
    await expect(validatePromoCode(SLUG, 'BADCODE')).rejects.toBeInstanceOf(NotFoundError)
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('createBooking', () => {
  const { start: bookingStart, end: bookingEnd } = futureDateRange(30, 3)
  const baseBody = {
    vehicleId: 'v-1',
    startDate: bookingStart.toISOString(),
    endDate: bookingEnd.toISOString(),
    firstName: 'Ali', lastName: 'Ben', email: 'ali@test.com',
    phone: '+212600000000',
    pickupLocation: 'Casablanca',
    returnLocation: 'Casablanca',
  }

  beforeEach(() => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findVehicleById).mockResolvedValue(makeVehicle() as any)
    vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: true, status: 'AVAILABLE', nextAvailableAt: null, blockingReason: null })
    vi.mocked(repo.upsertCustomer).mockResolvedValue({ id: 'c-1' } as any)
    vi.mocked(applyPricingRules).mockResolvedValue({ applied: [], total: 0 })
    vi.mocked(repo.createReservation).mockResolvedValue({ id: 'r-1' } as any)
    vi.mocked(repo.findReservationWithDetails).mockResolvedValue({
      id: 'r-1',
      bookingReference: 'BK-2026-ABC123',
      status: 'DRAFT',
      startDate: bookingStart,
      endDate: bookingEnd,
      pickupLocation: 'Casablanca',
      returnLocation: 'Casablanca',
      vehicle: { make: 'Toyota', model: 'Camry', year: 2022 },
      customer: { firstName: 'Ali', lastName: 'Ben', email: 'ali@test.com', phone: '+212600000000' },
      company: { name: 'Test Co', brand: { displayName: 'Test Co' } },
      additionalDrivers: [],
      insurances: [],
      totalAmount: 30000,
      paidAmount: 0,
      paymentStatus: 'UNPAID',
    } as any)
  })

  it('creates a booking and returns a pending guest confirmation', async () => {
    const result = await createBooking(SLUG, baseBody)
    expect(repo.createReservation).toHaveBeenCalledOnce()
    expect((result as any).status).toBe('PENDING')
    expect((result as any).bookingReference).toBe('BK-2026-ABC123')
  })

  it('throws AppError for invalid dates', async () => {
    await expect(
      createBooking(SLUG, { ...baseBody, startDate: '2026-07-04T00:00:00.000Z', endDate: '2026-07-01T00:00:00.000Z' }),
    ).rejects.toMatchObject({ error: 'invalid_dates' })
  })

  it('throws AppError when vehicle unavailable', async () => {
    vi.mocked(getVehicleAvailabilitySummary).mockResolvedValue({ available: false, status: 'RESERVED', nextAvailableAt: null, blockingReason: 'RESERVATION' })
    await expect(createBooking(SLUG, baseBody)).rejects.toMatchObject({ error: 'unavailable' })
  })
})

// ────────────────────────────────────────────────────────────────────────────
describe('initPayment — payment guard paths', () => {
  const payBody = { provider: 'PAYPAL' as const, successUrl: 'http://localhost:3000/ok', failureUrl: 'http://localhost:3000/fail', accessToken: 'booking-access-token-123' }

  it('throws AppError when reservation is already paid', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findReservationForPayment).mockResolvedValue({
      paymentStatus: 'PAID', totalAmount: 100,
      customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'A', lastName: 'B' },
      additionalDrivers: [],
      vehicle: { make: 'Toyota', model: 'Camry' },
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)

    await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'already_paid' })
  })

  it('throws AppError when license review is required', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findReservationForPayment).mockResolvedValue({
      paymentStatus: 'UNPAID', totalAmount: 100,
      customer: { licenseExpiry: null, licenseValidationStatus: 'DENIED', email: 'a@b.com', firstName: 'A', lastName: 'B' },
      additionalDrivers: [],
      vehicle: { make: 'Toyota', model: 'Camry' },
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)

    await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'license_review_required' })
  })

  it('throws AppError when PayPal is not configured', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findReservationForPayment).mockResolvedValue({
      paymentStatus: 'UNPAID', totalAmount: 30000,
      customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'Ali', lastName: 'Ben' },
      additionalDrivers: [],
      vehicle: { make: 'Toyota', model: 'Camry' },
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
    vi.mocked(paypalSvc.isConfigured).mockReturnValue(false)

    await expect(initPayment(SLUG, 'r-1', payBody)).rejects.toMatchObject({ error: 'provider_not_configured' })
  })

  it('returns checkoutUrl when PayPal is configured', async () => {
    vi.mocked(repo.findCompanyBySlug).mockResolvedValue(makeCompany() as any)
    vi.mocked(repo.findReservationForPayment).mockResolvedValue({
      id: 'r-1', paymentStatus: 'UNPAID', totalAmount: 30000, companyId: 'co-1',
      customer: { licenseExpiry: null, licenseValidationStatus: 'APPROVED', email: 'a@b.com', firstName: 'Ali', lastName: 'Ben' },
      additionalDrivers: [],
      vehicle: { make: 'Toyota', model: 'Camry' },
    } as any)
    vi.mocked(validateLicense).mockReturnValue({ status: 'VALID', requiresApproval: false } as any)
    vi.mocked(paypalSvc.isConfigured).mockReturnValue(true)
    vi.mocked(paypalSvc.createOrder).mockResolvedValue({ approveUrl: 'https://paypal.com/approve', orderId: 'pp-1' } as any)
    vi.mocked(repo.createRentalPayment).mockResolvedValue(undefined as any)

    const result = await initPayment(SLUG, 'r-1', payBody)
    expect(result.checkoutUrl).toBe('https://paypal.com/approve')
    expect(repo.createRentalPayment).toHaveBeenCalledOnce()
  })
})
