import { useRouter } from 'next/router'
import { useEffect, useMemo, useState } from 'react'
import { api } from 'src/api-helper'
import { pointToString, RP } from 'src/crypto/curve'
import { decryptWithRandomizer } from 'src/crypto/decrypt'
import { decryptSymmetric, importKeyFromBase64URL } from 'src/crypto/symmetric-encrypt'
import { Head } from 'src/Head'
import { unTruncateSelection } from 'src/status/un-truncate-selection'
import { useElectionInfo } from 'src/status/use-election-info'
import { TailwindPreflight } from 'src/TailwindPreflight'
import { generateColumnNames } from 'src/vote/generateColumnNames'
import { base64URLToBigint } from 'src/vote/submitted/base64url'

import { getAlreadySeenDevice, SameDeviceWarning } from './SameDeviceWarning'

type VoteData = Record<string, string> // questionId: plaintext

export function MalwareCheckPage() {
  return (
    <div className="p-5 mx-auto max-w-2xl sm:p-8">
      <Head title="Malware Check" />
      <PageContent />
      <TailwindPreflight />
    </div>
  )
}

function PageContent() {
  const router = useRouter()
  const { auth_token, election_id, otp } = router.query
  const [voteData, setVoteData] = useState<null | VoteData>(null)
  const [error, setError] = useState<string>('')
  const [confirmed, setConfirmed] = useState(false)
  const [issueDescription, setIssueDescription] = useState('')
  const [showIssueForm, setShowIssueForm] = useState(false)
  const [alreadySeenDevice, setAlreadySeenDevice] = useState<null | string>(null)
  const { ballot_design, electionInfoError, threshold_public_key } = useElectionInfo()

  // Extract symmetric key from URL hash and fetch encrypted payload
  useEffect(() => {
    if (typeof window === 'undefined' || !election_id || !auth_token || !otp || !threshold_public_key) return
    if (typeof otp !== 'string') return setError('Missing otp')
    if (typeof election_id !== 'string' || typeof auth_token !== 'string') return

    const hash = window.location.hash.slice(1) // Remove #
    if (!hash) return setError('Missing symmetric key in URL')
    ;(async () => {
      try {
        // Check if this device has been seen before
        const already_seen_device = getAlreadySeenDevice(election_id, auth_token, otp)
        setAlreadySeenDevice(already_seen_device)

        // Import symmetric key from base64URL
        const symmetricKey = await importKeyFromBase64URL(hash)

        // Fetch encrypted randomizers and vote from server
        const downloadResponse = await api('/malware-check/download', {
          already_seen_device,
          auth_token,
          election_id,
          otp,
        })
        if (downloadResponse.status === 410) return setError('Already used')
        if (!downloadResponse.ok) {
          const error = await downloadResponse.json().catch(() => ({ error: 'Failed to download encrypted vote data' }))
          return setError(error.error)
        }

        const { encrypted_randomizers, encrypted_vote } = await downloadResponse.json()
        if (!encrypted_randomizers) return setError('No encrypted randomizers received')
        if (!encrypted_vote) return setError('No encrypted vote received')

        // Decrypt randomizers using symmetric key
        const decryptedPayload = await decryptSymmetric(symmetricKey, encrypted_randomizers)

        // Parse pipe-delimited format: questionId|randomizer|questionId|randomizer|...
        const parts = decryptedPayload.split('|')
        if (parts.length < 2 || parts.length % 2 !== 0) throw new Error('Invalid data format')

        const randomizers: Record<string, string> = {}

        // Process in pairs: questionId, randomizer
        for (let i = 0; i < parts.length; i += 2) {
          const questionId = parts[i]
          const randomizer = parts[i + 1]
          randomizers[questionId] = randomizer
        }

        if (!randomizers || Object.keys(randomizers).length === 0) return setError('Invalid vote data format')

        // Decrypt each cipher using its randomizer
        const public_key = RP.fromHex(threshold_public_key)
        const decryptedPlaintext: Record<string, string> = {}

        for (const questionId of Object.keys(randomizers)) {
          const cipher = encrypted_vote[questionId]
          if (!cipher) return setError(`Missing encrypted vote for question: ${questionId}`)

          // Convert base64url encoded randomizer back to bigint
          const randomizerBigInt = base64URLToBigint(randomizers[questionId])

          // console.log({ cipher, public_key, questionId, randomizerBigInt })

          // Decrypt using randomizer and public key
          const decryptedPoint = decryptWithRandomizer(public_key, randomizerBigInt, {
            encrypted: RP.fromHex(cipher.encrypted),
            lock: RP.fromHex(cipher.lock),
          })

          // Extract plaintext from point (format: "verification_number:plaintext")
          const decodedString = pointToString(decryptedPoint)
          const colonIndex = decodedString.indexOf(':')
          if (colonIndex === -1) return setError(`Invalid decoded format for question: ${questionId}`)

          const plaintext = decodedString.slice(colonIndex + 1)
          decryptedPlaintext[questionId] = plaintext
        }

        setVoteData(decryptedPlaintext)

        // Report successful decryption to server
        const decryptSuccessResponse = await api('/malware-check/decrypt-success', {
          auth_token: auth_token as string,
          election_id: election_id as string,
          otp,
        })

        if (!decryptSuccessResponse.ok) console.warn('Failed to report decrypt success')
      } catch (error) {
        setError('Failed to decrypt or verify vote')
        console.error('Failed to decrypt or verify vote', error)
      }
    })()
  }, [election_id, auth_token, otp, threshold_public_key])

  const columns = useMemo(() => generateColumnNames({ ballot_design }).columns, [ballot_design])

  const handleConfirm = async (confirmed: boolean) => {
    try {
      const response = await api('/malware-check/confirm', {
        auth_token,
        confirmed,
        election_id,
        otp,
      })

      if (!confirmed) return setShowIssueForm(true)

      if (response.ok) setConfirmed(true)
    } catch (error) {
      // Handle error
      console.error('Failed to report confirmation', error)
    }
  }

  if (error === 'Already used')
    return (
      <div className="p-8">
        <h2 className="mb-4 font-bold text-red-600">Sorry</h2>
        <p>This 2nd Device Malware Check was already used.</p>

        <p className="mt-2 text-xl font-semibold">Restart from the original voting device.</p>
        <p className="mt-2">
          Refresh the original device&apos;s &quot;Vote Submitted&quot; screen to generate a new QR code.
        </p>

        {/* Why */}
        <div className="mt-8 text-sm">
          <p className="text-xs font-semibold opacity-50">Why?</p>
          <p>Each malware-check can only be used once.</p>
          <p className="mt-1">
            This protects against someone viewing the browser history to learn private vote data, when a borrowed device
            is used.
          </p>
        </div>
      </div>
    )

  if (error)
    return (
      <div className="p-8">
        <h2 className="mb-4 font-bold text-red-600">Error</h2>
        <p className="whitespace-pre-wrap">{error}</p>
      </div>
    )

  if (electionInfoError)
    return (
      <div className="p-8">
        <h2 className="mb-4 font-bold text-red-600">Error Loading Election</h2>
        <p className="whitespace-pre-wrap">
          {electionInfoError instanceof Error ? electionInfoError.message : String(electionInfoError)}
        </p>
        {election_id && <p className="mt-2 text-sm opacity-70">Election ID: {election_id}</p>}
      </div>
    )

  if (!voteData || !ballot_design || !threshold_public_key || !Object.keys(voteData).length)
    return (
      <div className="p-8 text-center animate-pulse">
        <p>Loading {!voteData ? 'vote data' : !ballot_design ? 'ballot design' : 'public key'}...</p>
      </div>
    )

  if (confirmed)
    return (
      <div className="p-8">
        <h2 className="mb-4 font-bold text-green-600">Thank you</h2>
        <p>You can now close this window.</p>
      </div>
    )

  // Show confirmation UI if match
  return (
    <div>
      <SameDeviceWarning {...{ alreadySeenDevice }} />
      <h1 className="mb-3 text-2xl font-semibold">Multi-Device Malware Check</h1>

      <h2 className="text-xl font-bold">Confirm your selections</h2>
      <h3 className="mb-4 text-xs tracking-wide opacity-70">
        FOR AUTH TOKEN: <span className="text-sm font-semibold">{auth_token}</span>
      </h3>

      <table className="mb-6 w-full border border-gray-300 border-collapse">
        <thead>
          <tr className="bg-gray-100">
            <th className="p-2 text-left border border-gray-300">Question</th>
            <th className="p-2 text-left border border-gray-300">Selection</th>
          </tr>
        </thead>
        <tbody>
          {columns.map((columnId) => {
            const plaintext = voteData?.[columnId]
            if (!plaintext) return null

            const displayValue = unTruncateSelection(plaintext, ballot_design || [], columnId)

            // Find question title from ballot design
            const question = ballot_design.find((item) => {
              if (item.id === columnId) return true
              // Handle multi-vote columns like "president_1", "president_2"
              const baseId = columnId.replace(/_\d+$/, '')
              return item.id === baseId
            })

            return (
              <tr key={columnId}>
                <td className="p-2 font-semibold border border-gray-300">{question?.title || columnId}</td>
                <td className="p-2 border border-gray-300">{plaintext === 'BLANK' ? '' : displayValue}</td>
              </tr>
            )
          })}
        </tbody>
      </table>

      <div className="mb-6">
        {!showIssueForm ? (
          <>
            <p className="mb-4">
              {Object.keys(columns).length === 1 ? 'Does this selection' : 'Do these selections'} match how you intended
              to vote?
            </p>
            <div className="flex gap-4">
              <button
                className="px-6 py-2 text-white bg-green-600 rounded hover:bg-green-700 active:bg-green-800"
                onClick={() => handleConfirm(true)}
              >
                Yes, match
              </button>
              <button
                className="px-6 py-2 text-white bg-red-600 rounded hover:bg-red-700 active:bg-red-800"
                onClick={() => handleConfirm(false)}
              >
                No, something is wrong
              </button>
            </div>
          </>
        ) : (
          <>
            <label className="block mb-2 font-semibold">Please describe the issue:</label>
            <textarea
              className="p-2 w-full rounded border border-gray-300"
              onChange={(e) => setIssueDescription(e.target.value)}
              rows={4}
              value={issueDescription}
            />
            <div className="flex gap-7 justify-end items-center mt-2">
              <button
                className="text-sm text-gray-400 rounded hover:text-gray-700"
                onClick={async () => {
                  await api('/malware-check/confirm', {
                    auth_token,
                    confirmed: false,
                    election_id,
                    issue_description: 'User pressed "Back" button',
                    otp,
                  })

                  setShowIssueForm(false)
                }}
              >
                Back
              </button>
              <button
                className="px-4 py-2 text-white bg-blue-500 rounded hover:bg-blue-600"
                onClick={async () => {
                  try {
                    const response = await api('/malware-check/confirm', {
                      auth_token,
                      confirmed: false,
                      election_id,
                      issue_description: issueDescription,
                      otp,
                    })

                    if (response.ok) {
                      setConfirmed(true)
                      setShowIssueForm(false)
                    }
                  } catch (error) {
                    // Handle error
                    console.error('Failed to submit issue', error)
                  }
                }}
              >
                Submit
              </button>
            </div>
          </>
        )}
      </div>
    </div>
  )
}
