import { ArrowRightOutlined, LoginOutlined } from '@ant-design/icons'
import { Button, Card, Checkbox, Col, Form, Input, Layout, notification, Row, Spin, Steps, Typography } from 'antd'
import CountryPhoneInput, { ConfigProvider } from 'antd-country-phone-input'
import { useForm } from 'antd/lib/form/Form'
import base64url from 'base64url'
import JSCookie from 'js-cookie'
import React, { useEffect, useState } from 'react'
import { useThemeSwitcher } from 'react-css-theme-switcher'
import OtpInput from 'react-otp-input'
import QRCode from 'react-qr-code'
import { useHistory } from 'react-router'
import useSWRImmutable from 'swr/immutable'
import { Api } from 'telegram'
import { generateRandomBytes } from 'telegram/Helpers'
import { computeCheck } from 'telegram/Password'
import en from 'world_countries_lists/data/countries/en/world.json'
import { fetcher, req } from '../utils/Fetcher'
import { anonymousTelegramClient, telegramClient } from '../utils/Telegram'
import { getUserLocale } from 'get-user-locale'

interface Props {
  me?: any
}

const Login: React.FC<Props> = ({ me }) => {
  const history = useHistory()
  const [formLogin] = useForm()
  const [formLoginQRCode] = useForm()
  const [currentStep, setCurrentStep] = useState<number>(0)
  const [phoneData, setPhoneData] = useState<{ phone?: string, code?: number, short?: string }>({})
  const [otp, setOtp] = useState<string>()
  const [loadingSendCode, setLoadingSendCode] = useState<boolean>()
  const [loadingLogin, setLoadingLogin] = useState<boolean>()
  const [countdown, setCountdown] = useState<number>()
  const [phoneCodeHash, setPhoneCodeHash] = useState<string>()
  const [needPassword, setNeedPassword] = useState<boolean>()
  const [method, setMethod] = useState<'phoneNumber' | 'qrCode'>('phoneNumber')
  const { data: _ } = useSWRImmutable('/utils/ipinfo', fetcher, { onSuccess: ({ ipinfo }) => setPhoneData(phoneData?.short ? phoneData : { short: ipinfo?.country || country }) })
  const [qrCode, setQrCode] = useState<{ loginToken: string, accessToken?: string, session?: string }>()
  const { currentTheme } = useThemeSwitcher()
  const [unknownNumber, setUnknownNumber] = useState<boolean>(false)
  const country = getUserLocale()?.substring(3, 5)?.toString() || 'ID'

  // useEffect(() => {
  //   // init config
  //   req.get('/config')
  // }, [])

  const sendCode = async (phoneNumber?: string) => {
    phoneNumber = phoneNumber || phoneData.phone ? `+${unknownNumber ? 888 : phoneData.code}${phoneData.phone}` : ''
    if (!phoneNumber) {
      return notification.error({
        message: 'Error',
        description: 'Please input your valid phone number with country code'
      })
    }

    const fetch = async (phoneCodeHash?: string) => {
      let data: any = null
      if (localStorage.getItem('experimental')) {
        const client = await anonymousTelegramClient.connect()
        if (phoneCodeHash) {
          const { phoneCodeHash: newPhoneCodeHash, timeout } = await client.invoke(new Api.auth.ResendCode({
            phoneNumber, phoneCodeHash
          }))
          const session = client.session.save() as any
          localStorage.setItem('session', session)
          data = { phoneCodeHash: newPhoneCodeHash, timeout }
        } else {
          const { phoneCodeHash, timeout } = await client.invoke(new Api.auth.SendCode({
            apiId: Number(process.env.REACT_APP_TG_API_ID),
            apiHash: process.env.REACT_APP_TG_API_HASH,
            phoneNumber,
            settings: new Api.CodeSettings({
              allowFlashcall: true,
              currentNumber: true,
              allowAppHash: true,
            })
          }))
          const session = client.session.save() as any
          localStorage.setItem('session', session)
          data = { phoneCodeHash, timeout }
        }
      } else {
        const invitationCode = location.search.replace('?code=', '')
        const resp = phoneCodeHash ? await req.post('/auth/reSendCode', { phoneNumber, phoneCodeHash, invitationCode }) : await req.post('/auth/sendCode', { phoneNumber, invitationCode })
        data = resp.data
      }
      setPhoneCodeHash(data.phoneCodeHash)
      setCountdown(data.timeout)
      notification.info({
        message: 'Sent!',
        description: 'Please check your Telegram app and input the code'
      })
    }

    try {
      setLoadingSendCode(true)
      await fetch(phoneCodeHash)
      setCurrentStep(1)
      setLoadingSendCode(false)
    } catch (error: any) {
      setLoadingSendCode(false)
      notification.error({
        message: 'Error',
        description: <>
          <Typography.Paragraph>
            {error?.response?.data?.error || error.message || 'Something error'}
          </Typography.Paragraph>
          <Typography.Paragraph code>
            {JSON.stringify(error?.response?.data || error?.data || error, null, 2)}
          </Typography.Paragraph>
        </>
      })
      if (error?.status === 400 || error?.response?.status === 400) {
        await fetch()
      }
    }
  }

  const login = async () => {
    if (!phoneCodeHash) {
      return sendCode()
    }
    setLoadingLogin(true)
    const phoneNumber = `+${unknownNumber ? 888 : phoneData.code}${phoneData.phone}`
    const phoneCode = otp
    const { password } = formLogin.getFieldsValue()
    try {
      let data: any = null
      if (localStorage.getItem('experimental')) {
        const client = await anonymousTelegramClient.connect()
        let signIn: any
        if (password) {
          const dataLogin = await client.invoke(new Api.account.GetPassword())
          dataLogin.newAlgo['salt1'] = Buffer.concat([dataLogin.newAlgo['salt1'], generateRandomBytes(32)])
          signIn = await client.invoke(new Api.auth.CheckPassword({ password: await computeCheck(dataLogin, password) }))
        } else {
          signIn = await client.invoke(new Api.auth.SignIn({ phoneNumber, phoneCode, phoneCodeHash }))
        }
        const userAuth = signIn['user']
        if (!userAuth) {
          return notification.error({ message: 'User not found/authorized' })
        }
        const session = client.session.save() as any
        localStorage.setItem('session', session)
        try {
          const resp = await req.get('/users/me')
          data = resp?.data
        } catch (error) {
          // ignore
        }
      } else {
        const invitationCode = location.search.replace('?code=', '')
        const resp = await req.post('/auth/login', { ...needPassword ? { password, invitationCode } : { phoneNumber, phoneCode, phoneCodeHash, invitationCode } })
        data = resp.data
      }
      try {
        // req.post('/users/me/paymentSync')
        if (localStorage.getItem('files')) {
          notification.info({
            key: 'sync',
            duration: null,
            message: 'Sync files data...',
            description: 'Please wait, we found your files data from another server.'
          })
          req.post('/files/filesSync', { files: JSON.parse(localStorage.getItem('files') || '[]') })
            .then(() => {
              notification.success({
                key: 'sync',
                duration: 4.5,
                message: 'Files data synced successfully',
                description: 'Reload your browser to see the migrated files.',
                btn: <Button href={window.location.href}>Reload</Button>
              })
              localStorage.removeItem('files')
            })
        }
      } catch (error) {
        // ignore
      }
      setLoadingLogin(false)
      notification.success({
        message: 'Success',
        description: `Welcome back, ${data.user.name || data.user.username}! Please wait a moment...`
      })
      if (localStorage.getItem('experimental')) {
        window.close()
      } else {
        history.replace('/dashboard')
      }
    } catch (error: any) {
      setLoadingLogin(false)
      let errorMessage = error?.errorMessage
      if (error.response && error.response.data) {
        errorMessage = error.response.data.details?.errorMessage
      }
      if (errorMessage === 'SESSION_PASSWORD_NEEDED') {
        notification.info({
          message: 'Info',
          description: 'Please input your 2FA password'
        })
        setCurrentStep(currentStep + 1)
        return setNeedPassword(true)
      }
      return notification.error({
        message: 'Error',
        description: <>
          <Typography.Paragraph>
            {error?.response?.data?.error || error.message || 'Something error'}
          </Typography.Paragraph>
          <Typography.Paragraph code>
            {JSON.stringify(error?.response?.data || error?.data || error, null, 2)}
          </Typography.Paragraph>
        </>
      })
    }
  }

  const _qrCodeSignIn = async (password?: string) => {
    let data: any = null
    const sessionString = qrCode?.session
    if (password && sessionString) {
      const client = await telegramClient.connect(sessionString || '')
      const passwordData = await client.invoke(new Api.account.GetPassword())

      passwordData.newAlgo['salt1'] = Buffer.concat([passwordData.newAlgo['salt1'], generateRandomBytes(32)])
      const signIn = await client.invoke(new Api.auth.CheckPassword({
        password: await computeCheck(passwordData, password)
      }))

      const userAuth = signIn['user']
      if (!userAuth) {
        throw { status: 400, body: { error: 'User not found/authorized' } }
      }

      const session = client.session.save() as any
      localStorage.setItem('session', session)
      const resp = await req.get('/users/me')
      data = resp.data
    } else {
      // handle the second call for export login token, result case: success, need to migrate to other dc, or 2fa
      const client = await telegramClient.connect(sessionString || '')
      try {
        const dataLogin = await client.invoke(new Api.auth.ExportLoginToken({
          apiId: Number(process.env.REACT_APP_TG_API_ID),
          apiHash: process.env.REACT_APP_TG_API_HASH,
          exceptIds: []
        }))

        // handle to switch dc
        if (dataLogin instanceof Api.auth.LoginTokenMigrateTo) {
          await client._switchDC(dataLogin.dcId)
          const result = await client.invoke(new Api.auth.ImportLoginToken({
            token: dataLogin.token
          }))

          // result import login token success
          if (result instanceof Api.auth.LoginTokenSuccess && result.authorization instanceof Api.auth.Authorization) {
            const userAuth = result.authorization.user
            if (userAuth) {
              const session = client.session.save() as any
              localStorage.setItem('session', session)
              const resp = await req.get('/users/me')
              data = resp.data
            } else {
              // ignore
            }
          } else {
            // ignore
          }

          // handle if success
        } else if (dataLogin instanceof Api.auth.LoginTokenSuccess && (dataLogin as any).authorization instanceof Api.auth.Authorization) {
          const userAuth = (dataLogin as any).authorization.user
          if (userAuth) {
            const session = client.session.save() as any
            localStorage.setItem('session', session)
            const resp = await req.get('/users/me')
            data = resp.data
          } else {
            // ignore
          }
        }
        data = {
          session: client.session.save() as any,
          loginToken: base64url(dataLogin['token'])
        }

      } catch (error: any) {
        // handle if need 2fa password
        if (error.errorMessage === 'SESSION_PASSWORD_NEEDED') {
          error.session = client.session.save() as any
        }
        throw error
      }
    }
    return data
  }

  const loginByQrCode = async () => {
    try {
      const { password } = formLoginQRCode.getFieldsValue()
      setLoadingLogin(true)
      const invitationCode = location.search.replace('?code=', '')
      const data = localStorage.getItem('experimental') ? await _qrCodeSignIn(password) : (await req.post('/auth/qrCodeSignIn', { password, session: qrCode?.session, invitationCode }))?.data
      try {
        // req.post('/users/me/paymentSync')
        if (localStorage.getItem('files')) {
          notification.info({
            key: 'sync',
            duration: null,
            message: 'Sync files data...',
            description: 'Please wait, we found your files data from another server.'
          })
          req.post('/files/filesSync', { files: JSON.parse(localStorage.getItem('files') || '[]') })
            .then(() => {
              notification.success({
                key: 'sync',
                duration: 4.5,
                message: 'Files data synced successfully',
                description: 'Reload your browser to see the migrated files.',
                btn: <Button href={window.location.href}>Reload</Button>
              })
              localStorage.removeItem('files')
            })
        }
      } catch (error) {
        // ignore
      }
      notification.success({
        message: 'Success',
        description: `Welcome back, ${data.user.name || data.user.username}! Please wait a moment...`
      })
      setLoadingLogin(false)
      if (localStorage.getItem('experimental')) {
        window.close()
      } else {
        history.replace('/dashboard')
      }
    } catch (error: any) {
      setLoadingLogin(false)
      return notification.error({
        message: 'Error',
        description: <>
          <Typography.Paragraph>
            {error?.response?.data?.error || error.message || 'Something error'}
          </Typography.Paragraph>
          <Typography.Paragraph code>
            {JSON.stringify(error?.response?.data || error?.data || error, null, 2)}
          </Typography.Paragraph>
        </>
      })
    }
  }

  useEffect(() => {
    if (JSCookie.get('authorization') && me?.user) {
      if (!localStorage.getItem('experimental')) {
        return history.replace('/dashboard')
      }
    }
  }, [me])

  useEffect(() => {
    if (countdown) {
      setTimeout(() => setCountdown(countdown - 1), 1000)
    } else {
      setLoadingSendCode(false)
    }
  }, [countdown])

  useEffect(() => {
    if (method === 'qrCode') {
      if (!qrCode?.loginToken) {
        if (localStorage.getItem('experimental')) {
          anonymousTelegramClient.connect()
            .then(client => {
              client.invoke(new Api.auth.ExportLoginToken({
                apiId: Number(process.env.REACT_APP_TG_API_ID),
                apiHash: process.env.REACT_APP_TG_API_HASH,
                exceptIds: []
              }))
                .then(data => {
                  const session = client.session.save() as any
                  localStorage.setItem('session', session)
                  setQrCode({
                    session: client.session.save() as any,
                    loginToken: base64url(data['token'])
                  })
                })
            })
        } else {
          req.get('/auth/qrCode').then(({ data }) => {
            setQrCode(data)
          })
        }
      }
    } else {
      setQrCode(undefined)
    }
  }, [method])

  useEffect(() => {
    if (qrCode && method === 'qrCode' && !needPassword) {
      const timeout = setTimeout(() => {
        if (method === 'qrCode' && !needPassword && qrCode?.loginToken) {
          new Promise((resolve, reject) => {
            if (localStorage.getItem('experimental')) {
              _qrCodeSignIn().then(resolve).catch(reject)
            } else {
              const invitationCode = location.search.replace('?code=', '')
              req.post('/auth/qrCodeSignIn', { invitationCode }, {
                headers: {
                  'Authorization': `Bearer ${qrCode.accessToken}`
                }
              }).then(({ data }) => resolve(data)).catch(reject)
            }
          }).then((data: any) => {
            // console.log(data)
            if (data?.user) {
              clearTimeout(timeout)
              try {
                // req.post('/users/me/paymentSync')
                if (localStorage.getItem('files')) {
                  notification.info({
                    key: 'sync',
                    duration: null,
                    message: 'Sync files data...',
                    description: 'Please wait, we found your files data from another server.'
                  })
                  req.post('/files/filesSync', { files: JSON.parse(localStorage.getItem('files') || '[]') })
                    .then(() => {
                      notification.success({
                        key: 'sync',
                        duration: null,
                        message: 'Files data synced successfully',
                        description: 'Reload your browser to see the migrated files.',
                        btn: <Button type="primary" href={window.location.href}>Reload</Button>
                      })
                      localStorage.removeItem('files')
                    })
                }
              } catch (error) {
                // ignore
              }
              notification.success({
                message: 'Success',
                description: `Welcome back, ${data.user.name || data.user.username}! Please wait a moment...`
              })
              if (localStorage.getItem('experimental')) {
                window.close()
              } else {
                history.replace('/dashboard')
              }
            } else {
              setQrCode(data)
            }
          }).catch((error: any) => {
            let errorMessage = error?.errorMessage
            if (error.response && error.response.data) {
              errorMessage = error.response.data.details?.errorMessage
            }
            if (errorMessage === 'SESSION_PASSWORD_NEEDED') {
              notification.info({
                message: 'Info',
                description: 'Please input your 2FA password'
              })

              let session = error.session
              if (!session && error.response && error.response.data) {
                session = error.response.data.details?.session
              }
              setQrCode({ ...qrCode, session })
              setNeedPassword(true)
            } else {
              // notification.error({
              //   message: 'Error',
              //   description: response?.data?.error || 'Something error'
              // })
            }
          })
        }
      }, 3000)
    }
  }, [qrCode, method])

  return <>
    <Layout.Content className="container">
      <Row style={{ marginTop: '30px' }}>
        <Col xxl={{ span: 8, offset: 8 }} xl={{ span: 8, offset: 8 }} lg={{ span: 10, offset: 7 }} md={{ span: 14, offset: 5 }} span={22} offset={1}>
          <Typography.Title level={2}>
            Login with Telegram
          </Typography.Title>
          <Typography.Paragraph type="secondary" style={{ fontSize: '14px' }}>
            Please download <a href="https://telegram.org/" target="_blank">Telegram</a> app and login with your account.
          </Typography.Paragraph>
          <Card>
            {method === 'phoneNumber' && <>
              <Steps current={currentStep} style={{ marginBottom: '35px' }} responsive>
                <Steps.Step key="inputPhoneNumber" title="Phone Number" />
                <Steps.Step key="inputCode" title="Code" />
                {needPassword && <Steps.Step key="inputPassword" title="Password 2FA" />}
              </Steps>
              <Form layout="vertical" form={formLogin} onFinish={login}>

                {currentStep === 0 && <>
                  <Form.Item>
                    <ConfigProvider locale={en}>
                      <CountryPhoneInput value={{ phone: phoneData.phone, code: unknownNumber ? 888 : phoneData.code, short: !unknownNumber ? phoneData.short || country : undefined }} type="tel" onChange={(e) => {
                        setPhoneData({ phone: e.phone, code: unknownNumber ? undefined : e.code, short: unknownNumber ? undefined : e.short })
                      }} />
                    </ConfigProvider>
                    {/* <Input.Search placeholder="+6289123456789" type="tel" enterButton={countdown ? `Re-send in ${countdown}s...` : phoneCodeHash ? 'Re-send' : 'Send'} loading={loadingSendCode} onSearch={sendCode} /> */}
                  </Form.Item>
                  <Form.Item>
                    <Checkbox checked={unknownNumber} onChange={({ target }) => setUnknownNumber(target.checked)}>
                      Use Fragment Number (+888).
                    </Checkbox>
                  </Form.Item>
                  <Form.Item style={{ marginTop: '50px', textAlign: 'center' }} wrapperCol={{ span: 24 }}>
                    <Button disabled={!phoneData?.phone} type="primary" size="large" htmlType="submit" icon={<ArrowRightOutlined />} shape="round" loading={loadingSendCode}>{phoneCodeHash ? 'Re-send code' : 'Send code'}</Button>
                  </Form.Item>
                  <Typography.Paragraph style={{ textAlign: 'center' }}>
                    <Button type="link" onClick={() => setMethod('qrCode')}>Login by QR Code</Button>
                  </Typography.Paragraph>
                </>}

                {currentStep === 1 && <>
                  <Form.Item style={{ textAlign: 'center' }}>
                    <Typography.Paragraph type="secondary">
                      Authentication code sent to <b>+{phoneData.code}&bull;&bull;&bull;&bull;&bull;&bull;&bull;{phoneData.phone?.substring(phoneData.phone.length - 4)}</b>
                    </Typography.Paragraph>
                    <OtpInput numInputs={5} value={otp as string || ''} onChange={setOtp} isInputNum containerStyle={{ justifyContent: 'center' }} inputStyle={{
                      width: '2.7rem',
                      height: '2.7rem',
                      margin: '0 0.3rem 1rem 0',
                      borderRadius: '4px',
                      fontSize: '1.2rem',
                      background: currentTheme === 'dark' ? 'rgba(255, 255, 255, 0.04)' : undefined,
                      border: '1px solid rgba(0, 0, 0, 0.3)'
                    }} />
                    {countdown ? <Typography.Paragraph type="secondary">Re-send in {countdown}s...</Typography.Paragraph> : <Typography.Paragraph>
                      <Button type="link" onClick={() => sendCode()}>Re-send code</Button>
                    </Typography.Paragraph>}
                  </Form.Item>
                  <Form.Item style={{ marginTop: '50px', textAlign: 'center' }} wrapperCol={{ span: 24 }}>
                    <Typography.Paragraph>
                      <Button disabled={otp?.length !== 5} shape="round" size="large" type="primary" htmlType="submit" loading={loadingLogin} icon={<LoginOutlined />}>Login</Button>
                    </Typography.Paragraph>
                    <Typography.Paragraph>
                      <Button type="link" onClick={() => {
                        setPhoneCodeHash(undefined)
                        setCurrentStep(currentStep - 1)
                      }}>Or, change phone number</Button>
                    </Typography.Paragraph>
                  </Form.Item>
                </>}

                {currentStep === 2 && <>
                  <Form.Item name="password" hidden={!needPassword}>
                    <Input.Password size="large" placeholder="password" />
                  </Form.Item>
                  <Form.Item style={{ marginTop: '50px', textAlign: 'center' }} wrapperCol={{ span: 24 }}>
                    <Button shape="round" size="large" type="primary" htmlType="submit" loading={loadingLogin} icon={<LoginOutlined />}>Login</Button>
                  </Form.Item>
                </>}
              </Form>
            </>}
            {method === 'qrCode' && <>
              <Layout>
                <Layout.Content>
                  {!needPassword ? <>
                    <Row align="middle" gutter={24}>
                      <Col span={24} lg={12}>
                        <Typography.Paragraph style={{ textAlign: 'center', marginBottom: '20px', background: '#fff', padding: '20px' }}>
                          {qrCode?.loginToken ? <QRCode size={165} value={`tg://login?token=${qrCode?.loginToken}`} /> : <Spin />}
                        </Typography.Paragraph>
                      </Col>
                      <Col span={24} lg={12}>
                        <Typography.Title level={5}>
                          Login to Telegram by QR Code
                        </Typography.Title>
                        <Typography.Paragraph>
                          <ol>
                            <li>Open Telegram on your phone</li>
                            <li>Go to <strong>Settings &gt; Devices &gt; Link Desktop Device</strong></li>
                            <li>Point your phone at this screen to confirm login</li>
                          </ol>
                          {/* <Collapse>
                            <Collapse.Panel key="1" header="How to log in">
                            </Collapse.Panel>
                          </Collapse> */}
                        </Typography.Paragraph>
                        <Typography.Paragraph style={{ textAlign: 'center' }}>
                          <Button type="link" onClick={() => setMethod('phoneNumber')}>Login by Phone Number</Button>
                        </Typography.Paragraph>
                      </Col>
                    </Row>
                    {/* <Row>
                      <Col lg={{ span: 14, offset: 5 }} md={{ span: 18, offset: 3 }} sm={{ span: 20, offset: 2 }} span={24}>
                      </Col>
                    </Row> */}
                  </> : <>
                    <Form form={formLoginQRCode} onFinish={loginByQrCode} layout="horizontal">
                      <Form.Item label="Password 2FA" name="password" hidden={!needPassword}>
                        <Input.Password placeholder="password" size="large" />
                      </Form.Item>
                      <Form.Item style={{ marginTop: '50px', textAlign: 'center' }} wrapperCol={{ span: 24 }}>
                        <Button shape="round" size="large" type="primary" htmlType="submit" loading={loadingLogin} icon={<LoginOutlined />}>Login</Button>
                      </Form.Item>
                    </Form>
                  </>}
                </Layout.Content>
              </Layout>
            </>}
          </Card>
        </Col>
      </Row>
    </Layout.Content>
  </>
}

export default Login
