import {
  describe,
  it,
  expect,
  jest,
  beforeEach,
  afterEach,
  beforeAll,
  afterAll,
} from "@jest/globals"
import { Request, Response, NextFunction } from "express"

const mockFetchSubscription = jest.fn<() => Promise<any>>()
const mockCreateCheckoutSession = jest.fn<() => Promise<string>>()
const mockCancelStripeSubscription = jest.fn<() => Promise<void>>()

jest.mock("@codeflash-ai/common", () => ({
  getSubscription: mockFetchSubscription,
  createCheckoutSession: mockCreateCheckoutSession,
  cancelSubscription: mockCancelStripeSubscription,
  prisma: {},
}))

import {
  getSubscription,
  createCheckout,
  cancelSubscription,
  setSubscriptionDependencies,
  resetSubscriptionDependencies,
  SubscriptionDependencies,
} from "../subscription-management"

type MockResponse = Partial<Response> & {
  status: jest.Mock
  json: jest.Mock
}

describe("Subscription Handlers", () => {
  let req: Partial<Request>
  let res: MockResponse
  let next: jest.Mock
  let mockDependencies: SubscriptionDependencies
  let originalConsoleError: typeof console.error

  beforeAll(() => {
    originalConsoleError = console.error
    console.error = jest.fn()
  })

  afterAll(() => {
    console.error = originalConsoleError
  })

  beforeEach(() => {
    jest.clearAllMocks()

    req = {
      query: {},
      body: {},
    }

    res = {
      status: jest.fn().mockReturnThis() as any,
      json: jest.fn().mockReturnThis() as any,
    }

    next = jest.fn()

    mockDependencies = {
      prisma: {
        subscriptions: {
          findUnique: jest.fn() as unknown as (params: any) => Promise<any>,
        },
      },
      fetchSubscription: jest.fn() as unknown as (userId: string) => Promise<any>, // Add this
      createCheckoutSession: jest.fn() as unknown as (
        userId: string,
        priceId: string,
        options: any,
      ) => Promise<string>,
      cancelStripeSubscription: jest.fn() as unknown as (userId: string) => Promise<void>,
      Sentry: {
        captureException: jest.fn(),
      },
    }

    setSubscriptionDependencies(mockDependencies)
  })

  afterEach(() => {
    jest.clearAllMocks()
    resetSubscriptionDependencies()
  })

  describe("getSubscription", () => {
    it("should call next with missingRequiredFields when userId is missing", async () => {
      req.query = {}

      await getSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(next).toHaveBeenCalledWith(
        expect.objectContaining({
          message: expect.stringContaining("Missing required fields"),
        }),
      )
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })

    it("should call next with subscriptionNotFound when subscription is not found", async () => {
      req.query = { userId: "user123" }
      ;(mockDependencies.fetchSubscription as any).mockResolvedValueOnce(null)

      await getSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.fetchSubscription).toHaveBeenCalledWith("user123")
      expect(next).toHaveBeenCalledWith(
        expect.objectContaining({
          message: expect.stringContaining("Subscription not found"),
        }),
      )
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })

    it("should return subscription details successfully", async () => {
      req.query = { userId: "user123" }

      const mockSubscription = {
        plan_type: "pro",
        subscription_status: "active",
        optimizations_used: 50,
        optimizations_limit: 100,
        current_period_end: "2024-12-31",
        total_lifetime_optimizations: 500,
      }

      ;(mockDependencies.fetchSubscription as any).mockResolvedValueOnce(mockSubscription)

      await getSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.fetchSubscription).toHaveBeenCalledWith("user123")
      expect(res.json).toHaveBeenCalledWith({
        plan: "pro",
        status: "active",
        usageCount: 50,
        usageLimit: 100,
        renewalDate: "2024-12-31",
        totalLifetimeOptimizations: 500,
      })
      expect(res.status).not.toHaveBeenCalled()
      expect(next).not.toHaveBeenCalled()
    })

    it("should handle errors and call next", async () => {
      req.query = { userId: "user123" }

      const error = new Error("Database error")

      ;(mockDependencies.fetchSubscription as any).mockRejectedValueOnce(error)

      await getSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.fetchSubscription).toHaveBeenCalledWith("user123")
      expect(mockDependencies.Sentry.captureException).toHaveBeenCalledWith(error)
      expect(next).toHaveBeenCalledWith(error)
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })
  })

  describe("createCheckout", () => {
    it("should call next with missingRequiredFields when userId is missing", async () => {
      req.body = { priceId: "price123" }

      await createCheckout(req as Request, res as Response, next as unknown as NextFunction)

      expect(next).toHaveBeenCalledWith(
        expect.objectContaining({
          message: expect.stringContaining("Missing required fields"),
        }),
      )
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })

    it("should call next with missingRequiredFields when priceId is missing", async () => {
      req.body = { userId: "user123" }

      await createCheckout(req as Request, res as Response, next as unknown as NextFunction)

      expect(next).toHaveBeenCalledWith(
        expect.objectContaining({
          message: expect.stringContaining("Missing required fields"),
        }),
      )
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })

    it("should create checkout session with default URLs", async () => {
      const originalEnv = process.env
      process.env = {
        ...originalEnv,
        WEBAPP_URL: "https://example.com",
        STRIPE_PRO_PRICE_YEARLY_ID: "yearly_price_id",
      }

      req.body = {
        userId: "user123",
        priceId: "price123",
      }

      const mockUrl = "https://checkout.stripe.com/session123"
      ;(mockDependencies.createCheckoutSession as any).mockResolvedValue(mockUrl)

      await createCheckout(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.createCheckoutSession).toHaveBeenCalledWith("user123", "price123", {
        successUrl: "https://example.com/app/billing?success=true",
        cancelUrl: "https://example.com/app/billing?canceled=true",
        period: "monthly",
      })
      expect(res.json).toHaveBeenCalledWith({ url: mockUrl })
      expect(next).not.toHaveBeenCalled()

      process.env = originalEnv
    })

    it("should create checkout session with custom URLs and yearly period", async () => {
      const originalEnv = process.env
      process.env = {
        ...originalEnv,
        STRIPE_PRO_PRICE_YEARLY_ID: "yearly_price_id",
      }

      req.body = {
        userId: "user123",
        priceId: "yearly_price_id",
        successUrl: "https://custom.com/success",
        cancelUrl: "https://custom.com/cancel",
        period: "yearly",
      }

      const mockUrl = "https://checkout.stripe.com/session123"
      ;(mockDependencies.createCheckoutSession as any).mockResolvedValue(mockUrl)

      await createCheckout(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.createCheckoutSession).toHaveBeenCalledWith(
        "user123",
        "yearly_price_id",
        {
          successUrl: "https://custom.com/success",
          cancelUrl: "https://custom.com/cancel",
          period: "yearly",
        },
      )
      expect(res.json).toHaveBeenCalledWith({ url: mockUrl })

      process.env = originalEnv
    })

    it("should handle errors and call next", async () => {
      req.body = {
        userId: "user123",
        priceId: "price123",
      }

      const error = new Error("Stripe error")
      ;(mockDependencies.createCheckoutSession as any).mockRejectedValue(error)

      await createCheckout(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.Sentry.captureException).toHaveBeenCalledWith(error)
      expect(next).toHaveBeenCalledWith(error)
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })
  })

  describe("cancelSubscription", () => {
    it("should call next with missingRequiredFields when userId is missing", async () => {
      req.body = {}

      await cancelSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(next).toHaveBeenCalledWith(
        expect.objectContaining({
          message: expect.stringContaining("Missing required fields"),
        }),
      )
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })

    it("should cancel subscription successfully", async () => {
      req.body = { userId: "user123" }
      ;(mockDependencies.cancelStripeSubscription as any).mockResolvedValue(undefined)

      await cancelSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.cancelStripeSubscription).toHaveBeenCalledWith("user123")
      expect(res.json).toHaveBeenCalledWith({ status: "canceled" })
      expect(next).not.toHaveBeenCalled()
    })

    it("should handle errors and call next", async () => {
      req.body = { userId: "user123" }

      const error = new Error("Cancellation error")
      ;(mockDependencies.cancelStripeSubscription as any).mockRejectedValue(error)

      await cancelSubscription(req as Request, res as Response, next as unknown as NextFunction)

      expect(mockDependencies.Sentry.captureException).toHaveBeenCalledWith(error)
      expect(next).toHaveBeenCalledWith(error)
      expect(res.status).not.toHaveBeenCalled()
      expect(res.json).not.toHaveBeenCalled()
    })
  })

  describe("Dependency Injection", () => {
    it("should allow setting partial dependencies", () => {
      const customSentry = {
        captureException: jest.fn(),
      }

      setSubscriptionDependencies({ Sentry: customSentry })

      customSentry.captureException("test")
      expect(customSentry.captureException).toHaveBeenCalledWith("test")
    })

    it("should reset dependencies to defaults", () => {
      const customSentry = {
        captureException: jest.fn(),
      }

      const originalCaptureException = mockDependencies.Sentry.captureException

      setSubscriptionDependencies({ Sentry: customSentry })

      customSentry.captureException("test")
      expect(customSentry.captureException).toHaveBeenCalledWith("test")

      resetSubscriptionDependencies()

      expect(originalCaptureException).not.toBe(customSentry.captureException)
    })
  })
})
