import { useEffect, useRef, useState } from 'react'
import type { BackendChallenge, BackendLimit, UploadEventType } from '../../functions/BackendTypes'
import { API_BASE, BackendDebounce, BackendFetch } from '../../functions/Backend'
import { KEY_PREFIX_UPLOAD, routeTo, setTitle } from '../../functions/Route'
import './styles/Upload.css'

import HeaderMessage from '../layout/HeaderMessage'
import InputFile, { type HandleForInputFile } from '../inputs/File'
import InputText, { type HandleForInputText } from '../inputs/Text'
import InputTags, { type HandleForInputTags } from '../inputs/Tags'
import InputButton from '../inputs/Button'
import FooterLoading from '../layout/FooterLoading'
import FooterError from '../layout/FooterError'
import FooterText from '../layout/FooterText'
import { wtEvent } from '../../functions/Watchtower'

export default function ViewUpload() {
    const solveChallengeRef = useRef<() => void>(null)
    const fileRef = useRef<HandleForInputFile>(null)
    const titleRef = useRef<HandleForInputText>(null)
    const tagsRef = useRef<HandleForInputTags>(null)

    const [workCounter, setWorkCounter] = useState(0)
    const [workNonce, setWorkNonce] = useState('')
    const [limits, setLimits] = useState<BackendLimit>()
    const [loading, setLoading] = useState(true)
    const [loadingReason, setLoadingReason] = useState<string>('')
    const [formError, setFormError] = useState<string>('')
    const [error, setError] = useState<string>('')

    async function onSubmit() {
        setFormError('')

        // Validate Client
        if (workCounter === 0) {
            wtEvent('upload_validation_fail', { reason: 'pow_unsolved' })
            return setFormError('Waiting for worker to complete')
        }
        if (!limits) {
            wtEvent('upload_validation_fail', { reason: 'restrictions_loading' })
            return setFormError('Limits not loaded yet')
        }

        // Validate Form
        const restrict = limits.upload
        const preview = fileRef.current?.getPreview()
        const file = fileRef.current?.getValue()
        const tags = tagsRef.current?.getValue()
        const title = titleRef.current?.getValue()

        if (!file || !preview) {
            wtEvent('upload_validation_fail', { reason: 'missing_file' })
            return setFormError('Please select a file.')
        }
        if (!tags || tags.length === 0) {
            wtEvent('upload_validation_fail', { reason: 'missing_tags' })
            return setFormError('Please add at least one tag.')
        }
        if (!title || title.trim().length === 0) {
            wtEvent('upload_validation_fail', { reason: 'missing_title' })
            return setFormError('Please enter a title.')
        }

        // Validate Media
        if (!restrict.mime_types.includes(file.type)) {
            wtEvent('upload_validation_fail', { reason: 'mime_type', value: file.type })
            return setFormError(`File type is not supported`)
        }
        if (file.size > restrict.filesize) {
            wtEvent('upload_validation_fail', { reason: 'file_size', value: file.size })
            return setFormError(`File exceeds ${Math.floor(restrict.filesize / 1024 / 1024)}MB limit`)
        }

        if (preview instanceof HTMLVideoElement) {
            if (preview.videoWidth < restrict.input_width_min || preview.videoHeight < restrict.input_height_min) {
                wtEvent('upload_validation_fail', {
                    reason: 'dimension_min',
                    width: preview.videoWidth,
                    height: preview.videoHeight,
                })
                return setFormError(
                    `Animation is too small (Min: ${restrict.input_width_min}x${restrict.input_height_min})`,
                )
            }
            if (preview.videoWidth > restrict.video_width_max || preview.videoHeight > restrict.video_height_max) {
                wtEvent('upload_validation_fail', {
                    reason: 'dimension_max',
                    width: preview.videoWidth,
                    height: preview.videoHeight,
                })
                return setFormError(
                    `Animation is too large (Max: ${restrict.video_width_max}x${restrict.video_height_max})`,
                )
            }
            if (preview.duration > restrict.duration) {
                wtEvent('upload_validation_fail', { reason: 'duration', width: preview.duration })
                return setFormError(`Animation is too long (Max: ${restrict.duration} seconds)`)
            }
        }

        if (preview instanceof HTMLImageElement) {
            if (preview.naturalWidth < restrict.input_width_min || preview.naturalHeight < restrict.input_height_min) {
                wtEvent('upload_validation_fail', {
                    reason: 'dimension_min',
                    width: preview.naturalWidth,
                    height: preview.naturalHeight,
                })
                return setFormError(
                    `Sticker is too small (Min: ${restrict.input_width_min}x${restrict.input_height_min})`,
                )
            }
            if (preview.naturalWidth > restrict.image_width_max || preview.naturalHeight > restrict.image_height_max) {
                wtEvent('upload_validation_fail', {
                    reason: 'dimension_max',
                    width: preview.naturalWidth,
                    height: preview.naturalHeight,
                })
                return setFormError(
                    `Sticker is too large (Max: ${restrict.image_width_max}x${restrict.image_height_max})`,
                )
            }
        }

        // Upload User Content
        setLoading(true)
        setLoadingReason('Uploading Content')

        const form = new FormData()
        form.append('file', file)
        form.append(
            'data',
            new Blob(
                [
                    JSON.stringify({
                        title: title.trim(),
                        tags: tags.map((t) => t.label),
                    }),
                ],
                { type: 'application/json' },
            ),
        )

        fetch(`${API_BASE}/uploads`, {
            body: form,
            method: 'POST',
            headers: {
                'X-Challenge-Counter': String(workCounter),
                'X-Challenge-Nonce': workNonce,
            },
        })
            .then(async (resp) => {
                let requestID = 'N/A'

                if (!resp.ok) {
                    solveChallengeRef.current?.() // refresh consumed pow

                    let reason = `Request failed: ${resp.status} ${resp.statusText}`
                    try {
                        const raw = await resp.text()
                        const dat = JSON.parse(raw)
                        if (dat.message) reason = `${dat.message} (${requestID})`
                    } catch {}

                    wtEvent('upload_fail', { reason })
                    setFormError(reason)
                    setLoading(false)
                    return
                }

                const reader = resp.body!.getReader()
                const decoder = new TextDecoder()
                let buffer = ''

                while (true) {
                    const { done, value } = await reader.read()
                    if (done) break

                    buffer += decoder.decode(value, { stream: true })
                    let parts = buffer.split('\n\n')
                    buffer = parts.pop()!

                    for (const part of parts) {
                        const lines = part.split('\n')
                        for (const line of lines) {
                            if (!line.startsWith('data:')) continue

                            const text = line.slice(5).trim()
                            if (!text) continue

                            const body: UploadEventType = JSON.parse(text)
                            switch (body.name) {
                                // Simple Messages
                                case 'id':
                                    requestID = body.data
                                    break
                                case 'step':
                                    setLoadingReason(body.data.message)
                                    break
                                case 'progress':
                                    setLoadingReason(`Processing: ${body.data.percent}%`)
                                    break

                                // Request Failed, Most likely due to NSFW
                                case 'error':
                                    wtEvent('upload_fail', { reason: body.data.message })
                                    setFormError(`${body.data.message} (${requestID})`)
                                    setLoading(false)
                                    break

                                // Request Complete
                                case 'finish':
                                    wtEvent('upload_success', {
                                        id: requestID,
                                        type: file.type,
                                        size: file.size,
                                        width:
                                            preview instanceof HTMLVideoElement
                                                ? preview.videoWidth
                                                : preview.naturalWidth,
                                        height:
                                            preview instanceof HTMLVideoElement
                                                ? preview.videoHeight
                                                : preview.naturalHeight,
                                        duration: preview instanceof HTMLVideoElement ? preview.duration : 0,
                                    })

                                    // Little hack to send the user to their personal gifs instead of the upload pane
                                    // when they click back... it just feels more logical.

                                    localStorage.setItem(`${KEY_PREFIX_UPLOAD}${requestID}`, body.data.edit_token)
                                    routeTo(`/personal`)
                                    routeTo(`/art/${requestID}`)
                                    break
                            }

                            // For debugging purposes
                            console.log(`[EVENT] ${body.name} => ${text}`)
                        }
                    }
                }
            })
            .catch((err) => {
                console.error('Network Error:', err)
                setFormError('Network Error')
                setLoading(false)
            })
            .finally(() => {
                solveChallengeRef.current?.()
            })
    }

    useEffect(() => {
        let iteration = 1
        async function handler() {
            const resp = await BackendFetch<BackendLimit>('/limits')
            if (resp.error) {
                console.log('Failed to fetch limits:', resp.error)
                const d = BackendDebounce(resp, iteration)
                if (!d.ratelimit) {
                    setError(resp.error)
                }
                await d.sleep
                setError('')
                handler()
                return
            }
            setLimits(resp.json)
        }
        handler()
    }, [])

    useEffect(() => {
        let timeout: number | undefined
        let worker: Worker
        let iteration = 1

        async function handler() {
            console.log('[WORKER] Starting Challenge')
            setLoadingReason('Retrieving Settings')

            // Request Challenge
            const resp = await BackendFetch<BackendChallenge>('/challenge?difficulty=20')
            if (resp.error) {
                console.log('[WORKER] Fetch Error:', resp.error)
                const d = BackendDebounce(resp, iteration)
                if (!d.ratelimit) {
                    setError(resp.error)
                }
                await d.sleep
                setError('')
                handler()
                return
            }
            setLoadingReason('Solving Anti-Spam Challenge')

            // Complete Challenge
            const { nonce, difficulty, expires } = resp.json
            const t = Date.now()

            worker.postMessage({ nonce, difficulty })
            worker.onmessage = (m: MessageEvent<{ counter: number }>) => {
                const tt = Date.now() - t
                wtEvent('upload_pow_solve', { difficulty, time: tt })

                console.log(`[WORKER] Work completed in ${tt}ms`)
                worker.onmessage = null
                setWorkCounter(m.data.counter)
                setWorkNonce(nonce)

                const tl = Math.max(expires * 1000 - Date.now(), 0)
                timeout = setTimeout(handler, tl)
                console.log(`[WORKER] Next Challenge in ${tl}ms`)

                setLoadingReason('Ready')
                setLoading(false)
            }
        }

        try {
            setLoadingReason('Spawning Worker')
            worker = new Worker('/worker-pow.js')
            solveChallengeRef.current = handler
            handler() // shouldn't fail
        } catch (error) {
            console.error('[WORKER] Startup Error:', error)
            setError('Init Error')
        }

        return () => {
            clearTimeout(timeout)
            worker.onmessage = null
            worker.terminate()
            console.log('[WORKER] Cleanup')
        }
    }, [])

    setTitle('Upload')
    return (
        <>
            <HeaderMessage label="View: Upload" />
            <div className="view-upload">
                <InputFile ref={fileRef} limits={limits?.upload} />
                <InputText ref={titleRef} label="Metadata: Title" placeholder="My Creation" />
                <InputTags ref={tagsRef} label="Metadata: Tags" allowCustom={true} onChange={undefined} />

                {!error && !loading && !formError && <FooterText label="" /* lazy divider */ />}
                {!error && loading && <FooterLoading reason={loadingReason} />}
                {!error && formError && <FooterError reason={formError} />}
                {error && <FooterError reason={error} />}

                <InputButton
                    id="action-upload"
                    label="Upload"
                    onClick={onSubmit}
                    disabled={loading || !!error}
                    rainbow={false}
                    selected={false}
                />
            </div>
        </>
    )
}
