'use client'
import { useEffect, useMemo, useState, useCallback, useRef } from 'react'
import webpush from 'web-push'
import { Button, FormHelperText, Alert, FormControl, InputLabel, OutlinedInput, InputAdornment, IconButton, Tooltip, ClickAwayListener, Tabs, CircularProgress } from '@mui/material'
import { ContentPasteGo as ContentPasteGoIcon } from '@mui/icons-material'
import ClearIcon from '@mui/icons-material/Clear'
const vapidPublicKey = 'BCog1diPhg4bePvGy-7k75yfmKqDTG9Z90aA6h9-3AAZ9ydYtF_HUYkoOyWF4dUi2-lb1fWbkojhaaS8P2UKaLA'
import axios, { AxiosError } from 'axios'
import { VerticalTabs, CustomMessageEditor } from '@/components'
import { User } from '@/types'
import jsSHA from "jssha"
import { checkNotificationStatus, requestNotificationPermission } from '@/utils/ios'
import { saveAccessKeyToIDB, loadAccessKeyFromIDB, removeAccessKeyFromIDB } from '@/hooks'

function generateSHA256Hash(data: string): string {
  const shaObj = new jsSHA("SHA-256", "TEXT");
  shaObj.update(data);
  return shaObj.getHash("HEX");
}

export default function useWebpush({ registerMode = 'allowed', os = 'Other', browser = 'Other', isStandalone = false }: { registerMode?: 'allowed' | 'install-required' | 'hidden', os?: string, browser?: string, isStandalone?: boolean } = {}){
	const [available, setAvailable] = useState(false)
	const [isSubscribed, setIsSubscribed] = useState<boolean | null>(null)
	const [errorMessage, setErrorMessage] = useState('')
	const [tooltipMessage, setTooltipMessagep] = useState<null | string>(null)
	const [accessKey, setAccessKey] = useState("")
	const [accessKeyError, setAccessKeyError] = useState<null | string>(null)
	const [users, setUsers] = useState<User[]>([])
	const [localHash, setLocalHash] = useState<null | string>(null)
	const [remoteHash, setRemoteHash] = useState<null | string>(null)
	const [isLoading, setIsLoading] = useState(true)
	const [pushSupported, setPushSupported] = useState<boolean | null>(null)
	const [state, setState] = useState<{status: string, token: string | null}>({ status: 'unknown', token: null })
	// 最後に checkAccessKey へ渡したキー。手入力 blur 時の二重照会 (ペースト → blur 等) を防ぐ。
	const lastCheckedAccessKeyRef = useRef('')

	// アクセスキーが入力されたらサーバへ問い合わせる
	const checkAccessKey = useCallback((newAccessKey: string) => {
		lastCheckedAccessKeyRef.current = newAccessKey
		setAccessKey(newAccessKey)
		setIsLoading(true)
		setAccessKeyError(null)
		return axios.get<{users: User[], hash: null | string}>('/api/mobile/'+newAccessKey)
		.then(({data}) => {
			setUsers(data.users)
			localStorage.setItem('Users', JSON.stringify(data.users))
			setIsLoading(false)
			setRemoteHash(data.hash)
			// 保存先は IndexedDB を primary とするが、Cookie へも書く。
			// iOS Safari→PWA は A2HS 時に Cookie のみ転送される (IDB は分離) ので、
			// 新規 iOS ユーザーが URL ハッシュ無しで A2HS した場合の bootstrap 救済として
			// Safari の Cookie jar に常に最新キーを置いておく。
			saveAccessKeyToIDB(newAccessKey)
			fetch('/accessKey', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ accessKey: newAccessKey }),
			})
			return data.hash
		})
		.catch(err => {
			setUsers([])
			console.error(err)
			if (axios.isAxiosError(err)) {
				const serverError = err as AxiosError<{error: string}>
				if (serverError && serverError.response) setAccessKeyError(serverError.response.data.error)
				else setAccessKeyError('接続に失敗しました')
			} else setAccessKeyError('不明なエラーが発生しました')
			setIsLoading(false)
			return null
		})
	}, [])

	// 初回読み込み時
	useEffect(() => {
		// アクセスキーは IndexedDB を primary store として管理するが、
		// iOS Safari→PWA の A2HS bootstrap 救済のため Cookie へも書く (best-effort)。
		const saveKey = async (key: string) => {
			await saveAccessKeyToIDB(key)
			fetch('/accessKey', {
				method: 'POST',
				headers: { 'Content-Type': 'application/json' },
				body: JSON.stringify({ accessKey: key }),
			})
		}

		const init = async () => {
			// 1. IndexedDB (primary) からアクセスキーを取得
			let idbKey: string | null = await loadAccessKeyFromIDB()

			// 2. URLハッシュのキーを取得 (deep link / QR / タブ復元で再適用するため URL に残す。
			//    A2HS の dialog で # が出ないのは PWA install criteria が満たされたときに
			//    ブラウザが manifest の start_url を採用するから。アドレスバーには # を残す)。
			//    キー変更をキャンセルした時のみ後段で消去する。
			const hashMatch = window.location.hash.match(/[#&]accessKey=([^&]+)/)
			const hashKey = hashMatch ? decodeURIComponent(hashMatch[1]) : null

			// 3. IndexedDB にもハッシュにも無ければ、旧保存先から IndexedDB へ取り込む。
			//    3a: Cookie (__Host-raikyakun_access)  3b: localStorage('AccessKey')
			//    Cookie は削除しない。iOS Safari→PWA の A2HS 時に Cookie のみが転送される
			//    ので、Safari の Cookie jar に最新キーを残し続けることが新規 PWA bootstrap
			//    の救済経路になる。
			if (!idbKey && !hashKey) {
				try {
					const res = await fetch('/accessKey')
					if (res.ok) {
						const data: { accessKey: string | null } = await res.json()
						if (data.accessKey) {
							await saveAccessKeyToIDB(data.accessKey)
							idbKey = data.accessKey
						}
					}
				} catch (e) {
					console.error('GET /accessKey failed', e)
				}
				if (!idbKey) {
					const lsKey = localStorage.getItem('AccessKey')
					if (lsKey && lsKey.length === 20) {
						await saveAccessKeyToIDB(lsKey)
						localStorage.removeItem('AccessKey')
						idbKey = lsKey
					}
				}
			}

			// WebViewの場合
			if (!('serviceWorker' in navigator)) {
				const resolvedKey = hashKey ?? idbKey
				if (hashKey && hashKey !== idbKey) await saveKey(hashKey)
				if (resolvedKey) setAccessKey(resolvedKey)
				setIsLoading(true)
				checkNotificationStatus()
				.then(res => {
					setState(res)
					if ((res.status === 'authorized' && !res.token) || res.status !== 'denied') {
						setIsSubscribed(false)
					}
					setIsLoading(false)
				})
				.catch(e => {
					console.error(e)
					setErrorMessage('ご利用できない環境です')
					setIsLoading(false)
				})
				return
			}

			// 4. SW初期化
			let registration: ServiceWorkerRegistration
			try {
				registration = await navigator.serviceWorker.ready
			} catch (err) {
				setErrorMessage(String(err))
				return
			}
			registration.update()

			if (!registration.pushManager) {
				setIsSubscribed(false)
				setPushSupported(false)
				const resolvedKey = hashKey ?? idbKey
				if (hashKey) await saveKey(hashKey)
				if (resolvedKey) {
					setAccessKey(resolvedKey)
					checkAccessKey(resolvedKey)
				} else {
					setIsLoading(false)
				}
				return
			}
			setPushSupported(true)

			let subscription: PushSubscription | null
			try {
				subscription = await registration.pushManager.getSubscription()
			} catch (err) {
				console.error('サブスクリプション取得エラー', err)
				return
			}

			// 5. キー変更 + サブスク登録済み → ユーザーに確認
			const isKeyChange = hashKey !== null && hashKey !== idbKey
			let keyChangeCancelled = false
			if (isKeyChange && subscription && idbKey) {
				if (confirm('現在別のアクセスキーで登録中です。登録解除してアクセスキーを変更しますか？')) {
					// 確認: フル解除 → 新キーで継続
					try {
						await unsubscribe({ isLocalOnly: false, accessKey: idbKey })
					} catch {
						setErrorMessage('登録解除に失敗しました。先に登録解除ボタンから解除してください。')
						return
					}
					await saveKey(hashKey)
					setAccessKey(hashKey)
					setUsers([])
					checkAccessKey(hashKey)
					return
				}
				// キャンセル: 旧キーのまま継続（IndexedDB は idbKey のまま）。
				// 拒否したキーが URL ハッシュに残ると reload のたびに再プロンプトになるため、
				// このケースだけハッシュを消去する。
				history.replaceState(null, '', location.pathname + location.search)
				keyChangeCancelled = true
			}

			// 6. キーの確定
			let resolvedKey: string | null
			if (keyChangeCancelled) {
				resolvedKey = idbKey
			} else if (hashKey) {
				// キー変更でサブスクなし、または同一キーの再アクセス
				await saveKey(hashKey)
				resolvedKey = hashKey
			} else {
				resolvedKey = idbKey
			}

			if (resolvedKey) setAccessKey(resolvedKey)

			if (!subscription) {
				setAvailable(true)
				setIsSubscribed(false)
				if (resolvedKey && resolvedKey.length === 20) checkAccessKey(resolvedKey)
				else setIsLoading(false)
			} else {
				setAvailable(false)
				setIsSubscribed(true)
				if (resolvedKey && resolvedKey.length === 20) {
					const localHash = generateSHA256Hash(subscription.endpoint)
					setLocalHash(localHash)
					checkAccessKey(resolvedKey).then(remoteHash => {
						if (localHash !== null && localHash !== remoteHash) {
							// ハッシュを比較して別端末で登録済みの場合はローカルのみ解除
							unsubscribe({ isLocalOnly: true, accessKey: resolvedKey! })
							.then(() => alert('別の端末で新しく登録されたため登録解除しました'))
							.catch(() => alert('別の端末で新しく登録されたため登録解除しようとしましたが失敗しました'))
						}
					})
				} else {
					setIsLoading(false)
				}
			}
		}

		const handleHashChange = () => {
			if (window.location.hash.match(/[#&]accessKey=([^&]+)/)) init()
		}
		window.addEventListener('hashchange', handleHashChange)
		init()
		return () => window.removeEventListener('hashchange', handleHashChange)
	}, [])



	const subscribe = useCallback(() => {
		if ('serviceWorker' in navigator) {
			navigator.serviceWorker.ready
			.then(registration => {
				registration.pushManager.subscribe({
					userVisibleOnly: true,
					applicationServerKey: urlBase64ToUint8Array(vapidPublicKey),
				})
				.then(async(subscription) => {
						const latestRemoteHash = await checkAccessKey(accessKey)
					if(latestRemoteHash && generateSHA256Hash(JSON.stringify(subscription)) != latestRemoteHash && !confirm('既に別の端末で登録されています。上書き登録しますか？')) return
					setIsSubscribed(true)
					
					axios.patch('/api/mobile/'+accessKey, {subscription, users: users.reduce((acc, item) => {
						const { id, isStealth } = item
						if(id) acc[id] = isStealth
						return acc
					}, {} as {[key: string]: boolean})})
					.catch((err: AxiosError<{error: string}>) => {
						// エラーの処理
						if (err.response?.data?.error) {
							// サーバーからの応答がある場合
							setErrorMessage(err.response.data.error)
						} else {
							// リクエストの設定中に何かが起こった場合
							setErrorMessage('エラーが発生しました:' + err.message)
						}
					})
				})
				.catch(err => setErrorMessage(err.toString()))
			})
			.catch(err => setErrorMessage(err.toString()))
		} else {
			// WebViewの場合
			setIsLoading(true)
			requestNotificationPermission()
			.then(res => {
				setState(res)
				setIsLoading(false)
			})
			.catch((e) => {
				console.error(e)
				setErrorMessage('ご利用できない環境です')
			})
		}
	}, [accessKey, users, remoteHash])

	const unsubscribe = useCallback(async (opts: {isLocalOnly: boolean, accessKey: string}) => {
		const registration = await navigator.serviceWorker.ready
		const subscription = await registration.pushManager.getSubscription()
		if (subscription) {
			try {
				if(opts && !opts.isLocalOnly){
					// サーバに登録している情報を削除する
					const localHash = generateSHA256Hash(subscription.endpoint)
					if(!opts.accessKey) throw new Error('アクセスキーがありません')
					
					await axios.patch('/api/mobile/'+opts.accessKey, {subscription: null, hash: localHash})
					.catch((err: AxiosError<{error: string}>) => {
						// エラーの処理
						if (err.response?.data?.error) {
							// サーバーからの応答がある場合
							throw new Error(err.response.data.error)
						} else {
							// リクエストの設定中に何かが起こった場合
							throw new Error(err.message)
						}
					})
					setRemoteHash(null)
					await subscription.unsubscribe()
					setIsSubscribed(false)
					return true
				} else {
					// ローカル情報のみ削除
					await subscription.unsubscribe()
					setIsSubscribed(false)
				}
			} catch(err){
				if (err instanceof Error) {
					setErrorMessage('エラー：' + err.message)
				} else {
					setErrorMessage('不明なエラーが発生しました')
				}
				throw err
			}
		} else {
			throw new Error('サブスクリプションを取得出来ませんでした')
		}
		return false
	}, [])

	const pasteFromClipboard = async() => {
		try {
			const text = await navigator.clipboard.readText()
			setTooltipMessagep('ペーストしました')
			checkAccessKey(text)
			// ここで取得したテキストを処理する
		} catch (error) {
			if (error instanceof Error) {
				if (error.name === 'NotAllowedError') {
					setTooltipMessagep('アクセスが許可されていません')
				} else {
					setTooltipMessagep('ペーストに失敗しました')
				}
			}
		}
	}
		// ボタンを押した時
	const handleClick = () => {
		requestNotificationPermission()
			.then((res) => {
				setState(res)
				// status が requested → 直後に OS がトークンを返すと
				// ScriptBridge が onNotificationUpdate() を再送してくるので
				// その都度 setState が呼ばれる
			})
			.catch((e) => console.error(e));
	}

	const canSubscribe = isSubscribed === false && !isLoading && users.length >= 1 && pushSupported !== false

	const Subscribe = (
		<>
			{ <>
				<div style={{position: 'relative', display: 'flex', justifyContent: 'center'}}>
					<FormControl variant="outlined" disabled={!!isSubscribed} sx={{maxWidth: '94%'}} error={!!accessKeyError}>
						<InputLabel htmlFor="url">アクセスキー</InputLabel>
						<OutlinedInput
							id="url"
							value={accessKey}
							startAdornment={os === 'Other' || isStandalone
								? <ClickAwayListener onClickAway={()=>setTooltipMessagep(null)}>
									<InputAdornment position="start">
										<Tooltip
											PopperProps={{disablePortal: true}} onClose={()=>setTooltipMessagep(null)} open={!!tooltipMessage}
											disableFocusListener disableHoverListener disableTouchListener
											placement="top" title={tooltipMessage}
										>
											<IconButton edge="start" onClick={pasteFromClipboard} disabled={!!isSubscribed || isLoading}>
												<ContentPasteGoIcon />
											</IconButton>
										</Tooltip>
									</InputAdornment>
								</ClickAwayListener>
								: undefined
							}
							endAdornment={os === 'Other' || isStandalone
								? <InputAdornment position="end">
									<IconButton edge="end" onClick={()=>{setAccessKey('');setAccessKeyError(null);setUsers([]);fetch('/accessKey', {method:'DELETE'});removeAccessKeyFromIDB()}} disabled={!!isSubscribed || isLoading}>
										<ClearIcon />
									</IconButton>
								</InputAdornment>
								: undefined
							}
							label="アクセスキー"
							placeholder="(タップしてペースト、または入力)"
							onPaste={e => !isLoading && !isSubscribed && checkAccessKey(e.clipboardData.getData('text'))}
							onChange={e => {
								// 手入力中は値の更新のみ。サーバ照会は blur 時にまとめて行う。
								if (isLoading || isSubscribed) return
								setAccessKey(e.target.value)
								setAccessKeyError(null)
							}}
							onBlur={() => {
								// 手入力でフォーカスが外れたタイミングで照会。
								// ペースト経路 (onPaste / ペーストボタン) は既に checkAccessKey 済みなので
								// lastCheckedAccessKeyRef との比較で二重照会を防ぐ。
								if (isLoading || isSubscribed) return
								if (accessKey && accessKey !== lastCheckedAccessKeyRef.current) checkAccessKey(accessKey)
							}}
							disabled={isLoading}
							inputProps={{ size: 20 }} sx={{fontFamily: 'monospace'}}
						/>
					</FormControl>
					{isLoading && <CircularProgress sx={{position: 'absolute', top: 0, bottom: 0, left: 0, right: 0, margin: 'auto'}} />}
				</div>
				<FormHelperText sx={{textAlign: 'center'}}>{!isSubscribed ? 'アクセスコードは管理者により発行されます' : '変更する場合はプッシュ通知登録解除してください'}</FormHelperText>
				{accessKeyError && <Alert severity="error" sx={{mt: 1}}>{accessKeyError}</Alert>}
				{(os === 'Other' || (isStandalone && (canSubscribe || isSubscribed === true))) && <VerticalTabs users={users} setUsers={setUsers} isSubscribed={isSubscribed} />}
				{(os === 'Other' ? isSubscribed === true : isStandalone && (canSubscribe || isSubscribed === true)) && <CustomMessageEditor />}
			</>}
			{!isSubscribed && <div style={{padding: '1rem'}}>
					{pushSupported === false && os === 'iOS' && browser !== 'Safari' && <>
						<FormHelperText sx={{mb: 1, color: 'warning.main'}}>プッシュ通知にはSafariで開く必要があります</FormHelperText>
						<Button fullWidth variant="outlined" component="a" href={`x-safari-${process.env.NEXT_PUBLIC_BASE_URL ?? 'https://mobile.raikyakun.app'}/${accessKey ? `#accessKey=${accessKey}` : ''}`}>Safariで開く</Button>
					</>}
					{pushSupported === false && os === 'Android' && <FormHelperText sx={{mb: 1, color: 'warning.main'}}>このブラウザはプッシュ通知に対応していません。Chromeでお開きください。</FormHelperText>}
				{pushSupported === false && os !== 'iOS' && os !== 'Android' && <FormHelperText sx={{mb: 1, color: 'warning.main'}}>このブラウザはプッシュ通知に対応していません。</FormHelperText>}
					{registerMode === 'allowed' && os === 'iOS' && browser === 'Safari' && !isStandalone && <FormHelperText sx={{mb: 1, color: 'warning.main'}}>プッシュ通知にはホーム画面への追加が必要です。画面下部の共有ボタン（□↑）から「ホーム画面に追加」を選んでください</FormHelperText>}
					{registerMode === 'allowed' && !(os === 'iOS' && browser === 'Safari' && !isStandalone) && pushSupported !== false && <Button fullWidth size="large" variant="contained" disabled={isSubscribed === null || isLoading || users.length < 1} onClick={subscribe}>プッシュ通知登録</Button>}
					{registerMode === 'install-required' && <FormHelperText sx={{textAlign: 'center', color: 'warning.main'}}>プッシュ通知を受け取るにはWebアプリとしてインストールしてください</FormHelperText>}
			</div>}
			{isSubscribed && registerMode !== 'hidden' && <div style={{padding: '1rem'}}>
				<Button fullWidth size="large" variant="outlined" color="error" disabled={isSubscribed === null} onClick={()=>unsubscribe({isLocalOnly: false, accessKey})}>プッシュ通知登録解除</Button>
			</div>}
		</>
	)

	return { Subscribe, isSubscribed, errorMessage, accessKey, canSubscribe, isLoading }
}

function urlBase64ToUint8Array(base64String: string) {
	const padding = '='.repeat((4 - (base64String.length % 4)) % 4);
	const base64 = (base64String + padding)
		.replace(/\-/g, '+')
		.replace(/_/g, '/')
	const rawData = window.atob(base64);
	return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
}

