import {createContext, useCallback, useContext} from 'react'
import {LayoutAnimation} from 'react-native'
import {
  ComAtprotoServerCreateAccount,
  type ComAtprotoServerDescribeServer,
} from '@atproto/api'
import {useLingui} from '@lingui/react/macro'
import * as EmailValidator from 'email-validator'

import {DEFAULT_SERVICE} from '#/lib/constants'
import {cleanError} from '#/lib/strings/errors'
import {createFullHandle} from '#/lib/strings/handles'
import {getAge} from '#/lib/strings/time'
import {useSessionApi} from '#/state/session'
import {useOnboardingDispatch} from '#/state/shell'
import {type AnalyticsContextType, useAnalytics} from '#/analytics'

export type ServiceDescription = ComAtprotoServerDescribeServer.OutputSchema

const date = new Date()
date.setFullYear(date.getFullYear() - 20) // default to 20 years ago
const DEFAULT_DATE = date

export enum SignupStep {
  INFO,
  HANDLE,
  CAPTCHA,
}

type SubmitTask = {
  verificationCode: string | undefined
  mutableProcessed: boolean // OK to mutate assuming it's never read in render.
}

type ErrorField =
  | 'invite-code'
  | 'email'
  | 'handle'
  | 'password'
  | 'date-of-birth'

export type SignupState = {
  analytics?: AnalyticsContextType

  hasPrev: boolean
  activeStep: SignupStep
  screenTransitionDirection: 'Forward' | 'Backward'

  serviceUrl: string
  serviceDescription?: ServiceDescription
  userDomain: string
  dateOfBirth: Date
  email: string
  password: string
  inviteCode: string
  handle: string

  error: string
  errorField?: ErrorField
  isLoading: boolean

  pendingSubmit: null | SubmitTask

  // Tracking
  signupStartTime: number
  fieldErrors: Record<ErrorField, number>
  backgroundCount: number
}

export type SignupAction =
  | {type: 'setAnalytics'; value: AnalyticsContextType}
  | {type: 'prev'}
  | {type: 'next'}
  | {type: 'finish'}
  | {type: 'setStep'; value: SignupStep}
  | {type: 'setServiceUrl'; value: string}
  | {type: 'setServiceDescription'; value: ServiceDescription | undefined}
  | {type: 'setEmail'; value: string}
  | {type: 'setPassword'; value: string}
  | {type: 'setDateOfBirth'; value: Date}
  | {type: 'setInviteCode'; value: string}
  | {type: 'setHandle'; value: string}
  | {type: 'setError'; value: string; field?: ErrorField}
  | {type: 'clearError'}
  | {type: 'setIsLoading'; value: boolean}
  | {type: 'submit'; task: SubmitTask}
  | {type: 'incrementBackgroundCount'}

export const initialState: SignupState = {
  analytics: undefined,

  hasPrev: false,
  activeStep: SignupStep.INFO,
  screenTransitionDirection: 'Forward',

  serviceUrl: DEFAULT_SERVICE,
  serviceDescription: undefined,
  userDomain: '',
  dateOfBirth: DEFAULT_DATE,
  email: '',
  password: '',
  handle: '',
  inviteCode: '',

  error: '',
  errorField: undefined,
  isLoading: false,

  pendingSubmit: null,

  // Tracking
  signupStartTime: Date.now(),
  fieldErrors: {
    'invite-code': 0,
    email: 0,
    handle: 0,
    password: 0,
    'date-of-birth': 0,
  },
  backgroundCount: 0,
}

export function is13(date: Date) {
  return getAge(date) >= 13
}

export function is18(date: Date) {
  return getAge(date) >= 18
}

export function reducer(s: SignupState, a: SignupAction): SignupState {
  let next = {...s}

  switch (a.type) {
    case 'setAnalytics': {
      next.analytics = a.value
      break
    }
    case 'prev': {
      if (s.activeStep !== SignupStep.INFO) {
        next.screenTransitionDirection = 'Backward'
        next.activeStep--
        next.error = ''
        next.errorField = undefined
      }
      break
    }
    case 'next': {
      if (s.activeStep !== SignupStep.CAPTCHA) {
        next.screenTransitionDirection = 'Forward'
        next.activeStep++
        next.error = ''
        next.errorField = undefined
      }
      break
    }
    case 'setStep': {
      next.activeStep = a.value
      break
    }
    case 'setServiceUrl': {
      next.serviceUrl = a.value
      break
    }
    case 'setServiceDescription': {
      LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)

      next.serviceDescription = a.value
      next.userDomain = a.value?.availableUserDomains[0] ?? ''
      next.isLoading = false
      break
    }

    case 'setEmail': {
      next.email = a.value
      break
    }
    case 'setPassword': {
      next.password = a.value
      break
    }
    case 'setDateOfBirth': {
      next.dateOfBirth = a.value
      break
    }
    case 'setInviteCode': {
      next.inviteCode = a.value
      break
    }
    case 'setHandle': {
      next.handle = a.value
      break
    }
    case 'setIsLoading': {
      next.isLoading = a.value
      break
    }
    case 'setError': {
      next.error = a.value
      next.errorField = a.field

      // Track field errors
      if (a.field) {
        next.fieldErrors[a.field] = (next.fieldErrors[a.field] || 0) + 1

        // Log the field error
        s.analytics?.metric('signup:fieldError', {
          field: a.field,
          errorCount: next.fieldErrors[a.field],
          errorMessage: a.value,
          activeStep: next.activeStep,
        })
      }
      break
    }
    case 'clearError': {
      next.error = ''
      next.errorField = undefined
      break
    }
    case 'submit': {
      next.pendingSubmit = a.task
      break
    }
    case 'incrementBackgroundCount': {
      next.backgroundCount = s.backgroundCount + 1

      // Log background/foreground event during signup
      s.analytics?.metric('signup:backgrounded', {
        activeStep: next.activeStep,
        backgroundCount: next.backgroundCount,
      })
      break
    }
  }

  next.hasPrev = next.activeStep !== SignupStep.INFO

  s.analytics?.logger.debug('signup', next)

  if (s.activeStep !== next.activeStep) {
    s.analytics?.logger.debug('signup: step changed', {
      activeStep: next.activeStep,
    })
  }

  return next
}

interface IContext {
  state: SignupState
  dispatch: React.Dispatch<SignupAction>
}
export const SignupContext = createContext<IContext>({} as IContext)
SignupContext.displayName = 'SignupContext'
export const useSignupContext = () => useContext(SignupContext)

export function useSubmitSignup() {
  const ax = useAnalytics()
  const {t: l} = useLingui()
  const {createAccount} = useSessionApi()
  const onboardingDispatch = useOnboardingDispatch()

  return useCallback(
    async (state: SignupState, dispatch: (action: SignupAction) => void) => {
      if (!state.email) {
        dispatch({type: 'setStep', value: SignupStep.INFO})
        return dispatch({
          type: 'setError',
          value: l`Please enter your email.`,
          field: 'email',
        })
      }
      if (!EmailValidator.validate(state.email)) {
        dispatch({type: 'setStep', value: SignupStep.INFO})
        return dispatch({
          type: 'setError',
          value: l`Your email appears to be invalid.`,
          field: 'email',
        })
      }
      if (!state.password) {
        dispatch({type: 'setStep', value: SignupStep.INFO})
        return dispatch({
          type: 'setError',
          value: l`Please choose your password.`,
          field: 'password',
        })
      }
      if (!state.handle) {
        dispatch({type: 'setStep', value: SignupStep.HANDLE})
        return dispatch({
          type: 'setError',
          value: l`Please choose your handle.`,
          field: 'handle',
        })
      }
      if (
        state.serviceDescription?.phoneVerificationRequired &&
        !state.pendingSubmit?.verificationCode
      ) {
        dispatch({type: 'setStep', value: SignupStep.CAPTCHA})
        ax.logger.error('Signup Flow Error', {
          errorMessage: 'Verification captcha code was not set.',
          registrationHandle: state.handle,
        })
        return dispatch({
          type: 'setError',
          value: l`Please complete the verification captcha.`,
        })
      }
      dispatch({type: 'setError', value: ''})
      dispatch({type: 'setIsLoading', value: true})

      try {
        await createAccount(
          {
            service: state.serviceUrl,
            email: state.email,
            handle: createFullHandle(state.handle, state.userDomain),
            password: state.password,
            birthDate: state.dateOfBirth,
            inviteCode: state.inviteCode.trim(),
            verificationCode: state.pendingSubmit?.verificationCode,
          },
          {
            signupDuration: Date.now() - state.signupStartTime,
            fieldErrorsTotal: Object.values(state.fieldErrors).reduce(
              (a, b) => a + b,
              0,
            ),
            backgroundCount: state.backgroundCount,
          },
        )

        /*
         * Must happen last so that if the user has multiple tabs open and
         * createAccount fails, one tab is not stuck in onboarding — Eric
         */
        onboardingDispatch({type: 'start'})
      } catch (err) {
        const e = err as Error
        let errMsg = e.toString()
        if (e instanceof ComAtprotoServerCreateAccount.InvalidInviteCodeError) {
          dispatch({
            type: 'setError',
            value: l`Invite code not accepted. Check that you input it correctly and try again.`,
            field: 'invite-code',
          })
          dispatch({type: 'setStep', value: SignupStep.INFO})
          return
        }

        const error = cleanError(errMsg)
        const isHandleError = error.toLowerCase().includes('handle')

        dispatch({type: 'setIsLoading', value: false})
        dispatch({
          type: 'setError',
          value: error,
          field: isHandleError ? 'handle' : undefined,
        })
        dispatch({type: 'setStep', value: isHandleError ? 2 : 1})

        ax.logger.error('Signup Flow Error', {
          errorMessage: error,
          registrationHandle: state.handle,
        })
      } finally {
        dispatch({type: 'setIsLoading', value: false})
      }
    },
    [l, ax.logger, createAccount, onboardingDispatch],
  )
}
