import { NextPage, NextPageContext } from 'next'
import { useRef, useState } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { Form, Formik } from 'formik'
import HCaptcha from '@hcaptcha/react-hcaptcha'

import { get } from '@utils/Query'
import { cleanObject, getRandom, parseCookie, redirectTo } from '@utils/Tools'
import { AddServerSubmitSchema, AddServerSubmit } from '@utils/Yup'
import { serverCategories, ServerIntroList } from '@utils/Constants'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ResponseProps, Server, ServerData, Theme, User } from '@types'
import Forbidden from '@components/Forbidden'

const CheckBox = dynamic(() => import('@components/Form/CheckBox'))
const Label = dynamic(() => import('@components/Form/Label'))
const Login = dynamic(() => import('@components/Login'))
const Input = dynamic(() => import('@components/Form/Input'))
const Divider = dynamic(() => import('@components/Divider'))
const TextArea = dynamic(() => import('@components/Form/TextArea'))
const Segment = dynamic(() => import('@components/Segment'))
const Markdown = dynamic(() => import('@components/Markdown'))
const Selects = dynamic(() => import('@components/Form/Selects'))
const Button = dynamic(() => import('@components/Button'))
const Container = dynamic(() => import('@components/Container'))
const Message = dynamic(() => import('@components/Message'))
const Captcha = dynamic(() => import('@components/Captcha'))

const AddServer: NextPage<AddServerProps> = ({
	logged,
	user,
	csrfToken,
	server,
	serverData,
	theme,
}) => {
	const [data, setData] = useState<ResponseProps<AddServerSubmit>>(null)
	const [captcha, setCaptcha] = useState(false)
	const [touchedSumbit, setTouched] = useState(false)
	const captchaRef = useRef<HCaptcha>()
	const router = useRouter()
	const initialValues: AddServerSubmit = {
		agree: false,
		invite: '',
		intro: '',
		desc: `<!-- 이 설명을 지우시고 원하시는 설명을 적으셔도 좋습니다! -->
# 서버 이름

자신의 서버를 자유롭게 표현해보세요!

## ✏️ 소개

무엇이 목적인 서버인가요?

어떤 주제인가요?

## 💬 특징

- 어떤
- 특징이
- 있나요?`,
		category: [],
		_csrf: csrfToken,
		_captcha: 'captcha',
	}

	function toLogin() {
		localStorage.redirectTo = window.location.href
		redirectTo(router, 'login')
	}

	async function submitServer(id: string, value: AddServerSubmit, token: string) {
		const res = await Fetch<AddServerSubmit>(`/servers/${id}`, {
			method: 'POST',
			body: JSON.stringify(cleanObject<AddServerSubmit>({ ...value, _captcha: token })),
		})
		setData(res)
	}

	if (!logged)
		return (
			<Login>
				<NextSeo
					title='새로운 서버 추가하기'
					description='자신의 서버를 한국 디스코드 리스트에 등록하세요.'
					openGraph={{
						title: '새로운 서버 추가하기',
						description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.',
					}}
				/>
			</Login>
		)
	if (data?.data && data.code == 200) {
		setTimeout(() => redirectTo(router, `/servers/${router.query.id}`), 1_000)
	}
	return (
		<Container paddingTop className='py-5'>
			<NextSeo
				title='새로운 서버 추가하기'
				description='자신의 서버를 한국 디스코드 리스트에 등록하세요.'
				openGraph={{
					title: '새로운 서버 추가하기',
					description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.',
				}}
			/>
			<h1 className='text-3xl font-bold'>새로운 서버 추가하기</h1>
			<div className='mb-5 mt-1'>
				안녕하세요,{' '}
				<span className='font-semibold'>
					{user.username}#{user.tag}
				</span>
				님!{' '}
				<a
					role='button'
					tabIndex={0}
					onKeyDown={toLogin}
					onClick={toLogin}
					className='cursor-pointer text-discord-blurple outline-none'
				>
					본인이 아니신가요?
				</a>
			</div>
			{data ? (
				data.code == 200 && data.data ? (
					<Message type='success'>
						<h2 className='text-lg font-extrabold'>서버 등록 성공!</h2>
						<p>서버를 성공적으로 등록했습니다! 서버 페이지로 리다이렉트 됩니다!</p>
					</Message>
				) : (
					<Message type='error'>
						<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
						<ul className='list-inside list-disc'>
							{data.errors?.map((el, n) => <li key={n}>{el}</li>)}
						</ul>
					</Message>
				)
			) : (
				<></>
			)}
			{server ? (
				<Message type='warning'>
					<h2 className='text-lg font-extrabold'>이미 등록된 서버입니다.</h2>
				</Message>
			) : !serverData ? (
				<Message type='info'>
					<h2 className='text-lg font-extrabold'>서버에 봇이 초대되지 않았습니다.</h2>
					<p>서버를 등록하시려면 먼저 봇을 초대해야합니다.</p>
					<p>서버에 이미 봇이 초대되었다면 반영까지 최대 1분이 소요될 수 있습니다.</p>
				</Message>
			) : serverData.admins.includes(user.id) || serverData.owner.includes(user.id) ? (
				<Formik
					initialValues={initialValues}
					validationSchema={AddServerSubmitSchema}
					onSubmit={() => setCaptcha(true)}
				>
					{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
						<Form>
							<div className='py-3'>
								<Message type='warning'>
									<h2 className='text-lg font-extrabold'>
										등록하시기 전에 다음 사항을 확인해 주세요!
									</h2>
									<ul className='list-inside list-disc'>
										<li>
											<Link
												href='/discord'
												rel='noreferrer'
												target='_blank'
												className='text-blue-500 hover:text-blue-600'
											>
												디스코드 서버
											</Link>
											에 참여를 권장드립니다.
										</li>
										<li>
											서버가{' '}
											<Link
												href='/guidelines'
												rel='noreferrer'
												target='_blank'
												className='text-blue-500 hover:text-blue-600'
											>
												가이드라인
											</Link>
											을 지키고 있나요?
										</li>
										<li>
											서버에서 <strong>관리자</strong> 권한을 갖고 있는 모든 분은 삭제를 제외한 모든
											행동을 할 수 있습니다.
										</li>
										<li>
											서버를 등록한 이후 봇을 추방하시게 되면 서버 정보가 웹에 업데이트 되지
											않습니다.
										</li>
										<li>
											또한, 서버를 등록하게 되면 작성하신 모든 정보와 서버에서 수집된 정보는 웹과
											API에 공개됩니다.
										</li>
									</ul>
								</Message>
							</div>
							<Label
								For='agree'
								error={errors.agree && touched.agree ? errors.agree : null}
								grid={false}
							>
								<div className='flex items-center'>
									<CheckBox name='agree' />
									<strong className='ml-2 text-sm'>
										해당 내용을 숙지하였으며, 등록 이후에 가이드라인을 위반할시 서버가 웹에서 삭제될
										수 있다는 점을 확인했습니다.
									</strong>
								</div>
							</Label>
							<Divider />
							<Label For='id' label='서버' labelDesc='등록하시는 대상 서버 입니다.'>
								<p>
									<strong>{serverData.name}</strong>
									<br /> ID: {router.query.id}
								</p>
							</Label>
							<Divider />
							<Label
								For='category'
								label='카테고리'
								labelDesc='서버에 해당되는 카테고리를 선택해주세요'
								required
								error={errors.category && touched.category ? (errors.category as string) : null}
							>
								<Selects
									options={serverCategories.map((el) => ({ label: el, value: el }))}
									handleChange={(value) => {
										setFieldValue(
											'category',
											value.map((v) => v.value)
										)
									}}
									handleTouch={() => setFieldTouched('category', true)}
									values={values.category as string[]}
									setValues={(value) => setFieldValue('category', value)}
								/>
								<span className='mt-1 text-sm text-gray-400'>
									서버 카드에는 앞 3개의 카테고리만 표시됩니다. 드래그하여 카테고리를 정렬하세요.{' '}
									<strong>반드시 해당되는 카테고리만 선택해주세요.</strong>
								</span>
							</Label>
							<Label
								For='invite'
								label='서버 초대코드'
								labelDesc='서버의 초대코드를 입력해주세요. (만료되지 않는 코드로 입력해주세요!)'
								error={errors.invite && touched.invite ? errors.invite : null}
								short
								required
							>
								<div className='flex items-center'>
									discord.gg/
									<Input name='invite' placeholder='JEh53MQ' />
								</div>
							</Label>
							<Divider />
							<Label
								For='intro'
								label='서버 소개'
								labelDesc='서버를 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)'
								error={errors.intro && touched.intro ? errors.intro : null}
								required
							>
								<Input name='intro' placeholder={getRandom(ServerIntroList)} />
							</Label>
							<Label
								For='desc'
								label='서버 설명'
								labelDesc={
									<>
										서버를 자세하게 설명해주세요! (최대 1500자)
										<br />
										마크다운을 지원합니다!
									</>
								}
								error={errors.desc && touched.desc ? errors.desc : null}
								required
							>
								<TextArea
									max={1500}
									name='desc'
									placeholder='서버에 대해 최대한 자세히 설명해주세요!'
									theme={theme === 'dark' ? 'dark' : 'light'}
									value={values.desc}
									setValue={(value) => setFieldValue('desc', value)}
								/>
							</Label>
							<Label
								For='preview'
								label='설명 미리보기'
								labelDesc='다음 결과는 실제와 다를 수 있습니다.'
							>
								<Segment>
									<Markdown text={values.desc} />
								</Segment>
							</Label>
							<Divider />
							<p className='mb-5 mt-2 text-base'>
								<span className='font-semibold text-red-500'> *</span> = 필수 항목
							</p>
							{captcha ? (
								<Captcha
									ref={captchaRef}
									dark={theme === 'dark'}
									onVerify={(token) => {
										submitServer(router.query.id as string, values, token)
										window.scrollTo({ top: 0 })
										setCaptcha(false)
										captchaRef?.current?.resetCaptcha()
									}}
								/>
							) : (
								<>
									{touchedSumbit && !isValid && (
										<div className='my-1 text-xs font-light text-red-500'>
											누락되거나 잘못된 항목이 있습니다. 다시 확인해주세요.
										</div>
									)}
									<Button
										type='submit'
										onClick={() => {
											setTouched(true)
											if (!isValid) window.scrollTo({ top: 0 })
										}}
									>
										<>
											<i className='far fa-paper-plane' /> 등록
										</>
									</Button>
								</>
							)}
						</Form>
					)}
				</Formik>
			) : (
				<Forbidden />
			)}
		</Container>
	)
}

export const getServerSideProps = async (ctx: NextPageContext) => {
	const parsed = parseCookie(ctx.req)
	const user = await get.Authorization(parsed?.token)
	const server = (await get.server.load(ctx.query.id as string)) || null
	const serverData = (await get.serverData(ctx.query.id as string)) || null
	return {
		props: {
			logged: !!user,
			user: await get.user.load(user || ''),
			csrfToken: getToken(ctx.req, ctx.res),
			server,
			serverData:
				+new Date() - +new Date(serverData?.updatedAt) < 2 * 60 * 1000 ? serverData : null,
		},
	}
}

interface AddServerProps {
	logged: boolean
	user: User
	csrfToken: string
	server: Server | null
	serverData: ServerData | null
	theme: Theme
}

export default AddServer
