import Cookies from 'js-cookie'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import * as amplitude from '@/app/components/base/amplitude'
import { AppModeEnum } from '@/types/app'
import {
  buildCreateAppEventPayload,
  extractExternalCreateAppAttribution,
  rememberCreateAppExternalAttribution,
  trackCreateApp,
} from '../create-app-tracking'

describe('create-app-tracking', () => {
  beforeEach(() => {
    vi.restoreAllMocks()
    vi.spyOn(amplitude, 'trackEvent').mockImplementation(() => {})
    window.sessionStorage.clear()
    window.history.replaceState({}, '', '/apps')
  })

  describe('extractExternalCreateAppAttribution', () => {
    it('should map campaign links to external attribution', () => {
      const attribution = extractExternalCreateAppAttribution({
        searchParams: new URLSearchParams('utm_source=x&slug=how-to-build-rag-agent'),
      })

      expect(attribution).toEqual({
        utmSource: 'twitter/x',
        utmCampaign: 'how-to-build-rag-agent',
      })
    })

    it('should map newsletter and blog sources to blog', () => {
      expect(extractExternalCreateAppAttribution({
        searchParams: new URLSearchParams('utm_source=newsletter'),
      })).toEqual({ utmSource: 'blog' })

      expect(extractExternalCreateAppAttribution({
        utmInfo: { utm_source: 'dify_blog', slug: 'launch-week' },
      })).toEqual({
        utmSource: 'blog',
        utmCampaign: 'launch-week',
      })
    })
  })

  describe('rememberCreateAppExternalAttribution', () => {
    it('should ignore malformed utm cookies', () => {
      vi.spyOn(Cookies, 'get').mockImplementation(((key?: string) => {
        return key ? 'not-json' : {}
      }) as typeof Cookies.get)

      expect(rememberCreateAppExternalAttribution()).toBeNull()
      expect(window.sessionStorage.getItem('create_app_external_attribution')).toBeNull()
    })
  })

  describe('buildCreateAppEventPayload', () => {
    it('should build payloads with source, normalized app mode, and timestamp', () => {
      expect(buildCreateAppEventPayload({
        source: 'studio_blank',
        appMode: AppModeEnum.ADVANCED_CHAT,
      }, null, new Date(2026, 3, 13, 14, 5, 9))).toEqual({
        source: 'studio_blank',
        app_mode: 'chatflow',
        time: '04-13-14:05:09',
      })
    })

    it('should map agent mode into the canonical app mode bucket', () => {
      expect(buildCreateAppEventPayload({
        source: 'studio_blank',
        appMode: AppModeEnum.AGENT_CHAT,
      }, null, new Date(2026, 3, 13, 9, 8, 7))).toEqual({
        source: 'studio_blank',
        app_mode: 'agent',
        time: '04-13-09:08:07',
      })
    })

    it('should fold legacy non-agent modes into chatflow', () => {
      expect(buildCreateAppEventPayload({
        source: 'studio_blank',
        appMode: AppModeEnum.CHAT,
      }, null, new Date(2026, 3, 13, 8, 0, 1))).toEqual({
        source: 'studio_blank',
        app_mode: 'chatflow',
        time: '04-13-08:00:01',
      })

      expect(buildCreateAppEventPayload({
        source: 'studio_blank',
        appMode: AppModeEnum.COMPLETION,
      }, null, new Date(2026, 3, 13, 8, 0, 2))).toEqual({
        source: 'studio_blank',
        app_mode: 'chatflow',
        time: '04-13-08:00:02',
      })
    })

    it('should map workflow mode into the workflow bucket', () => {
      expect(buildCreateAppEventPayload({
        source: 'studio_blank',
        appMode: AppModeEnum.WORKFLOW,
      }, null, new Date(2026, 3, 13, 7, 6, 5))).toEqual({
        source: 'studio_blank',
        app_mode: 'workflow',
        time: '04-13-07:06:05',
      })
    })

    it('should include template_id for template sources', () => {
      expect(buildCreateAppEventPayload({
        source: 'studio_template_list',
        appMode: AppModeEnum.CHAT,
        templateId: 'template-1',
      }, null, new Date(2026, 3, 13, 8, 0, 1))).toEqual({
        source: 'studio_template_list',
        app_mode: 'chatflow',
        time: '04-13-08:00:01',
        template_id: 'template-1',
      })
    })

    it('should prefer external attribution when present', () => {
      expect(buildCreateAppEventPayload(
        {
          source: 'studio_template_list',
          appMode: AppModeEnum.WORKFLOW,
          templateId: 'template-1',
        },
        {
          utmSource: 'linkedin',
          utmCampaign: 'agent-launch',
        },
        new Date(2026, 3, 13, 7, 6, 5),
      )).toEqual({
        source: 'external',
        app_mode: 'workflow',
        time: '04-13-07:06:05',
        template_id: 'template-1',
        utm_source: 'linkedin',
        utm_campaign: 'agent-launch',
      })
    })

    it('should not build external payloads without attribution', () => {
      expect(buildCreateAppEventPayload({
        source: 'external',
        appMode: AppModeEnum.WORKFLOW,
      }, null, new Date(2026, 3, 13, 7, 6, 5))).toBeNull()
    })
  })

  describe('trackCreateApp', () => {
    it('should track remembered external attribution once before falling back to internal source', () => {
      rememberCreateAppExternalAttribution({
        searchParams: new URLSearchParams('utm_source=newsletter&slug=how-to-build-rag-agent'),
      })

      trackCreateApp({ source: 'studio_template_list', appMode: AppModeEnum.WORKFLOW, templateId: 'template-1' })

      expect(amplitude.trackEvent).toHaveBeenNthCalledWith(1, 'create_app', {
        source: 'external',
        app_mode: 'workflow',
        time: expect.stringMatching(/^\d{2}-\d{2}-\d{2}:\d{2}:\d{2}$/),
        template_id: 'template-1',
        utm_source: 'blog',
        utm_campaign: 'how-to-build-rag-agent',
      })

      trackCreateApp({ source: 'studio_template_list', appMode: AppModeEnum.WORKFLOW, templateId: 'template-1' })

      expect(amplitude.trackEvent).toHaveBeenNthCalledWith(2, 'create_app', {
        source: 'studio_template_list',
        app_mode: 'workflow',
        time: expect.stringMatching(/^\d{2}-\d{2}-\d{2}:\d{2}:\d{2}$/),
        template_id: 'template-1',
      })
    })

    it('should keep using remembered external attribution after navigating away from the original url', () => {
      window.history.replaceState({}, '', '/apps?utm_source=linkedin&slug=agent-launch')

      rememberCreateAppExternalAttribution({
        searchParams: new URLSearchParams(window.location.search),
      })

      window.history.replaceState({}, '', '/explore')

      trackCreateApp({ source: 'explore_template_preview', appMode: AppModeEnum.CHAT, templateId: 'template-2' })

      expect(amplitude.trackEvent).toHaveBeenCalledWith('create_app', {
        source: 'external',
        app_mode: 'chatflow',
        time: expect.stringMatching(/^\d{2}-\d{2}-\d{2}:\d{2}:\d{2}$/),
        template_id: 'template-2',
        utm_source: 'linkedin',
        utm_campaign: 'agent-launch',
      })
    })

    it('should fall back to the provided source when window is unavailable', () => {
      const originalWindow = globalThis.window

      try {
        Object.defineProperty(globalThis, 'window', {
          configurable: true,
          value: undefined,
        })

        trackCreateApp({ source: 'studio_blank', appMode: AppModeEnum.AGENT_CHAT })

        expect(amplitude.trackEvent).toHaveBeenCalledWith('create_app', {
          source: 'studio_blank',
          app_mode: 'agent',
          time: expect.stringMatching(/^\d{2}-\d{2}-\d{2}:\d{2}:\d{2}$/),
        })
      }
      finally {
        Object.defineProperty(globalThis, 'window', {
          configurable: true,
          value: originalWindow,
        })
      }
    })

    it('should read, normalize, and consume snake_case sessionStorage attribution', () => {
      window.sessionStorage.setItem('create_app_external_attribution', JSON.stringify({
        utm_source: 'twitter',
        utm_campaign: 'launch-week',
      }))

      trackCreateApp({ source: 'studio_blank', appMode: AppModeEnum.CHAT })

      expect(amplitude.trackEvent).toHaveBeenCalledWith('create_app', {
        source: 'external',
        app_mode: 'chatflow',
        time: expect.stringMatching(/^\d{2}-\d{2}-\d{2}:\d{2}:\d{2}$/),
        utm_source: 'twitter/x',
        utm_campaign: 'launch-week',
      })
      expect(window.sessionStorage.getItem('create_app_external_attribution')).toBeNull()
    })

    it('should not track external source without remembered attribution', () => {
      trackCreateApp({ source: 'external', appMode: AppModeEnum.WORKFLOW, templateId: 'template-1' })

      expect(amplitude.trackEvent).not.toHaveBeenCalled()
    })
  })
})
