import React, { useContext, createContext, useReducer, Provider, useCallback, useEffect, useMemo, Suspense } from "react"
import { Route, Routes, useLocation, useNavigate } from "react-router-dom";
import { encrypt, decrypt } from './encryption'
import env from "../env";
import { flash } from "./toast";

const Login = React.lazy(() => import('./login'))
const SignUp = React.lazy(() => import('./signup'))
const Home = React.lazy(() => import('./home'))
const ResetPassword = React.lazy(() => import('./reset-password'))

const jsonRegex = /^application\/json/
const textRegex = /^text\/plain/
const request = async (url: string, method: string, body: any, token: string | null, key?: string, iv?: string, stream?: boolean) => {
  const headers = new Headers();
  headers.append('device-id', localStorage.getItem('device_id') || '')
  if (token) {
    headers.append('Authorization', `Bearer ${token}`);
    if (method !== 'GET') {
      headers.append('Content-Type', key && iv ? 'text/plain' : 'application/json');
      if (key && iv) body = `~e~${encrypt(typeof body === 'object' ? JSON.stringify(body) : body, key, iv)}`;
      else if (typeof body === 'object') body = JSON.stringify(body);
    }
  } else if (method !== 'GET') {
    headers.append('Content-Type', 'application/json');
    body = typeof body === 'object' ? JSON.stringify(body) : body;
  }
  return fetch(env.API_HOST + url, {
    method,
    headers,
    body
  }).then(async response => {
    if (!response.ok) throw new Error(response.statusText)
    if (textRegex.test(response.headers.get('content-type') || '')) {
      return response.text().then(text => {
        if (text.startsWith('~e~') && key && iv) {
          let o = null
          try {
            o = decrypt(text.slice(3), key, iv)
          } catch { o = null }
          if (o) {
            try {
              return JSON.parse(o)
            } catch { return o }
          }
          return text
        }
        return text
      })
    } else if (jsonRegex.test(response.headers.get('content-type') || '')) {
      return response.json()
    } else if (response.body && stream) {
      return response.body
    } else {
      return response
    }
  })
}
const AppContext = createContext(null);
export const useAppContext = () => useContext(AppContext);
type AppState = {
  token: string | null,
  error: string | null,
  loading: boolean,
  key: string | null,
  iv: string | null
}
const AppProvider: Provider<any> = AppContext.Provider
export default function App() {
  const [state, setState] = useReducer((state: AppState, newState: AppState) => ({ ...state, ...newState }), {
    token: null,
    key: null,
    iv: null,
    error: null,
    loading: false
  }) as any
  
  const location = useLocation()
  const { pathname, search } = location
  const queries = useMemo(() => {
    const q = new URLSearchParams(search)
    const o: {[key: string]: string} = {}
    q.forEach((v, k) => o[k] = v)
    return o
  }, [search])
  const redirect = useNavigate()

  const encryptData = useCallback((data: string | object) => {
    if (!state.key || !state.iv) throw new Error('Key or IV not set')
    const text = typeof data === 'object' ? JSON.stringify(data) : data
    return encrypt(text, state.key, state.iv)
  }, [state.key, state.iv])

  const decryptData = useCallback((data: string) => {
    if (!state.key || !state.iv) throw new Error('Key or IV not set')
    return decrypt(data, state.key, state.iv)
  }, [state.key, state.iv])

  const requestData = useCallback((url: string, method: string, body: any, useAuth: boolean = false) => {
    if (useAuth && !state.token) throw new Error('Please login first')
    return request(url, method, body, useAuth ? state.token : null, state.key, state.iv)
  }, [state.token, state.key, state.iv])

  const logout = useCallback(() => {
    requestData('/invalidate', 'POST', {}, true).catch(() => { })
    localStorage.removeItem('token')
    setState({ iv: null, token: null, key: null })
  }, [setState, requestData])

  useEffect(() => {
    let loggedIn = Boolean(state.token)
    if (!loggedIn) {
      const tokenInfo = localStorage.getItem('token')
      if (tokenInfo) {
        try {
          const { key, iv, token } = JSON.parse(tokenInfo)
          if (key && iv && token) {
            setState({ token, key, iv })
            loggedIn = true
          }
        } catch {
          localStorage.removeItem('token')
        }
      }
    } else {
      localStorage.setItem('token', JSON.stringify({ token: state.token, key: state.key, iv: state.iv }))
    }
    if (!loggedIn && pathname !== '/login' && pathname !== '/sign-up' && pathname !== '/reset-password') {
      redirect('/login')
    } else if (loggedIn && (pathname === '/login' || pathname === '/sign-up' || pathname === '/reset-password')) {
      redirect('/')
    }
  }, [state.token, state.key, state.iv, pathname])

  return (
    <AppProvider value={{
      flash,
      state,
      setState,
      request: requestData,
      encrypt: encryptData,
      decrypt: decryptData,
      logout,
      pathname,
      queries,
      redirect
    }}>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="*" element={<Home />} />
          <Route path="/login" element={<Login />} />
          <Route path="/sign-up" element={<SignUp />} />
          <Route path="/reset-password" element={<ResetPassword />} />
        </Routes>
      </Suspense>
    </AppProvider>
  )
}