"use client"

import type { JSX, ReactNode } from "react"

import { useQueryClient } from "@tanstack/react-query"
import Cookies from "js-cookie"
import { createContext, useContext, useEffect, useRef, useState } from "react"
import { useDispatch } from "react-redux"

import { useAppSelector } from "@shared/hooks/useAppSelector"
import { API_URL } from "@shared/lib/constants"
import {
	addNotification,
	NotificationItem,
	setNotifications,
} from "@shared/store/notifications"
import { getProjectWorkspaceQueryKey } from "@shared/api/projectWorkflow"

interface SocketContextValue {
	isConnected: boolean
}

const SocketContext = createContext<SocketContextValue>({ isConnected: false })

export const useSocket = () => useContext(SocketContext)

const MOCK_NOTIFICATIONS: NotificationItem[] = [
	{
		event: "task_update",
		notification_id: null,
		job_id: "mock-1",
		project_id: null,
		job_type: "TRANSCRIPTION_GENERATE",
		status: "RUNNING",
		progress_pct: 30,
		message: "Транскрибирование (whisper)",
		title: "Транскрипция",
		created_at: new Date().toISOString(),
	},
	{
		event: "task_update",
		notification_id: null,
		job_id: "mock-1",
		project_id: null,
		job_type: "TRANSCRIPTION_GENERATE",
		status: "RUNNING",
		progress_pct: 60,
		message: "Транскрибирование (whisper)",
		title: "Транскрипция",
		created_at: new Date().toISOString(),
	},
	{
		event: "task_update",
		notification_id: "mock-notif-1",
		job_id: "mock-1",
		project_id: null,
		job_type: "TRANSCRIPTION_GENERATE",
		status: "DONE",
		progress_pct: 100,
		message: "Завершено",
		title: "Транскрипция",
		created_at: new Date().toISOString(),
	},
	{
		event: "task_update",
		notification_id: null,
		job_id: "mock-2",
		project_id: null,
		job_type: "MEDIA_CONVERT",
		status: "RUNNING",
		progress_pct: 50,
		message: "Конвертация",
		title: "Конвертация",
		created_at: new Date().toISOString(),
	},
	{
		event: "task_update",
		notification_id: "mock-notif-2",
		job_id: "mock-2",
		project_id: null,
		job_type: "MEDIA_CONVERT",
		status: "FAILED",
		progress_pct: null,
		message: "Ошибка конвертации",
		title: "Конвертация",
		created_at: new Date().toISOString(),
	},
	{
		event: "task_update",
		notification_id: "mock-notif-3",
		job_id: "mock-3",
		project_id: null,
		job_type: "SILENCE_REMOVE",
		status: "DONE",
		progress_pct: 100,
		message: "Завершено",
		title: "Удаление тишины",
		created_at: new Date().toISOString(),
	},
]

const MAX_RECONNECT_ATTEMPTS = 10
const BASE_DELAY_MS = 1000

/** Shape returned by GET /api/notifications/ */
interface NotificationReadDTO {
	id: string
	job_id: string | null
	project_id: string | null
	notification_type: string
	title: string
	message: string | null
	payload: { job_type?: string; status?: string; progress_pct?: number } | null
	is_read: boolean
	created_at: string
}

function mapDTOtoItem(dto: NotificationReadDTO): NotificationItem {
	return {
		event: "task_update",
		notification_id: dto.id,
		job_id: dto.job_id,
		project_id: dto.project_id,
		job_type: dto.payload?.job_type ?? null,
		status: dto.payload?.status ?? null,
		progress_pct: dto.payload?.progress_pct ?? null,
		message: dto.message,
		title: dto.title,
		created_at: dto.created_at,
		is_read: dto.is_read,
	}
}

export const SocketProvider = ({
	children,
}: {
	children: ReactNode
}): JSX.Element => {
	const [isConnected, setIsConnected] = useState(false)
	const dispatch = useDispatch()
	const queryClient = useQueryClient()
	const userData = useAppSelector((state) => state.user.user)
	const wsRef = useRef<WebSocket | null>(null)
	const reconnectAttempts = useRef(0)
	const reconnectTimer = useRef<ReturnType<typeof setTimeout> | null>(null)
	const hydratedRef = useRef(false)

	// Hydrate persisted notifications from the backend on mount
	useEffect(() => {
		if (!userData || hydratedRef.current) return
		hydratedRef.current = true

		const token = Cookies.get("access_token")
		if (!token) return

		const baseUrl = API_URL || "http://localhost:8000"
		fetch(`${baseUrl}/api/notifications/`, {
			headers: { Authorization: `Bearer ${token}` },
		})
			.then((res) => (res.ok ? res.json() : []))
			.then((data: NotificationReadDTO[]) => {
				if (!data.length) return
				// Backend returns newest-first. Deduplicate by job_id,
				// keeping only the latest (first encountered) notification per job.
				const seen = new Set<string>()
				const unique: NotificationItem[] = []
				for (const dto of data) {
					if (dto.job_id) {
						if (seen.has(dto.job_id)) continue
						seen.add(dto.job_id)
					}
					unique.push(mapDTOtoItem(dto))
				}
				dispatch(setNotifications(unique))
			})
			.catch(() => {
				// Silently ignore hydration errors — WS will still deliver live updates
			})
	}, [userData, dispatch])

	useEffect(() => {
		if (!userData) return

		const isMock = process.env.NEXT_PUBLIC_MOCK_WS === "true"

		if (isMock) {
			let idx = 0
			const interval = setInterval(() => {
				if (idx < MOCK_NOTIFICATIONS.length) {
					dispatch(addNotification(MOCK_NOTIFICATIONS[idx]))
					idx++
				} else {
					clearInterval(interval)
				}
			}, 2000)
			setIsConnected(true)
			return () => {
				clearInterval(interval)
				setIsConnected(false)
			}
		}

		const connect = () => {
			const token = Cookies.get("access_token")
			if (!token) return

			const wsUrl =
				process.env.NEXT_PUBLIC_WS_URL || "ws://localhost:8000"
			const ws = new WebSocket(
				`${wsUrl}/api/notifications/ws/?token=${token}`,
			)
			wsRef.current = ws

			ws.onopen = () => {
				setIsConnected(true)
				reconnectAttempts.current = 0
			}

			ws.onmessage = (event) => {
				try {
					const data = JSON.parse(event.data) as NotificationItem
					dispatch(addNotification(data))

					if (
						data.status === "DONE" &&
						data.job_type === "TRANSCRIPTION_GENERATE"
					) {
						queryClient.invalidateQueries({
							queryKey: ["get", "/api/transcribe/transcriptions/"],
						})
						queryClient.invalidateQueries({
							queryKey: ["get", "/api/media/artifacts/"],
						})
						queryClient.invalidateQueries({
							queryKey: ["get", "/api/files/files/"],
						})
					}

					if (
						data.status === "DONE" &&
						data.job_type === "MEDIA_CONVERT"
					) {
						queryClient.invalidateQueries({
							queryKey: ["get", "/api/media/artifacts/"],
						})
						queryClient.invalidateQueries({
							queryKey: ["get", "/api/files/files/"],
						})
					}

					if (data.project_id) {
						queryClient.invalidateQueries({
							queryKey: getProjectWorkspaceQueryKey(data.project_id),
						})
					}
				} catch {
					// Ignore malformed messages
				}
			}

			ws.onclose = () => {
				setIsConnected(false)
				wsRef.current = null

				if (reconnectAttempts.current < MAX_RECONNECT_ATTEMPTS) {
					const delay =
						BASE_DELAY_MS *
						Math.pow(2, reconnectAttempts.current)
					reconnectAttempts.current++
					reconnectTimer.current = setTimeout(connect, delay)
				}
			}

			ws.onerror = () => {
				ws.close()
			}
		}

		connect()

		return () => {
			if (reconnectTimer.current) {
				clearTimeout(reconnectTimer.current)
			}
			if (wsRef.current) {
				wsRef.current.close()
			}
		}
	}, [userData, dispatch])

	return (
		<SocketContext.Provider value={{ isConnected }}>
			{children}
		</SocketContext.Provider>
	)
}
