import React, { Component } from 'react'
import FlipMove from 'react-flip-move'
import autosize from 'autosize'
import { marked } from 'marked'

import i18n from './i18n'
import './style/index.styl'
import {
  queryParse,
  queryStringify,
  axiosJSON,
  axiosCodeberg,
  getMetaContent,
  formatErrorMsg,
  hasClassInParent
} from './util'
import Avatar from './component/avatar'
import Button from './component/button'
import Action from './component/action'
import Comment from './component/comment'
import Svg from './component/svg'
import { CT_ACCESS_TOKEN, CT_VERSION, CT_COMMENT } from './const'

class CotalkComponent extends Component {
  state = {
    user: null,
    issue: null,
    comments: [],
    localComments: [],
    comment: '',
    page: 1,
    pagerDirection: 'last',
    cursor: null,
    previewHtml: '',

    isNoInit: false,
    isIniting: true,
    isCreating: false,
    isLoading: false,
    isLoadMore: false,
    isLoadOver: false,
    isIssueCreating: false,
    isPopupVisible: false,
    isInputFocused: false,
    isPreview: false,

    isOccurError: false,
    errorMsg: '',
  }

  constructor (props) {
    super(props)
    this.options = Object.assign({}, {
      id: window.location.href,
      number: -1,
      labels: ['Cotalk'],
      title: window.document.title,
      body: '',
      language: window.navigator.language || window.navigator.userLanguage,
      perPage: 10,
      pagerDirection: 'last',
      createIssueManually: false,
      distractionFreeMode: false,
      proxy: 'https://cors-anywhere.cli.gv.uy/?https://codeberg.org/login/oauth/access_token',
      flipMoveOptions: {
        staggerDelayBy: 150,
        appearAnimation: 'accordionVertical',
        enterAnimation: 'accordionVertical',
        leaveAnimation: 'accordionVertical',
      },
      enableHotKey: true,

      url: window.location.href,

      defaultAuthor: {
        avatarUrl: 'https://codeberg.org/avatars/f52f9e0a1abec56148ef594f6b72c28acba2a5c8b1618df4a7c0579275e863f5?size=50',
        login: 'null',
        url: '',
      },

      updateCountCallback: null
    }, props.options)

    this.state.pagerDirection = this.options.pagerDirection
    const storedComment = window.localStorage.getItem(CT_COMMENT)
    if (storedComment) {
      this.state.comment = decodeURIComponent(storedComment)
      window.localStorage.removeItem(CT_COMMENT)
    }

    // 处理隐式流程（hash 中的 access_token）
    const hashStr = window.location.hash.replace(/^#/, '')
    if (hashStr) {
      const hashParams = new URLSearchParams(hashStr)
      const accessToken = hashParams.get('access_token')
      if (accessToken) {
        this.accessToken = accessToken
        history.replaceState(null, null, window.location.pathname + window.location.search)
        this.getInit()
          .then(() => this.setState({ isIniting: false }))
          .catch(err => {
            console.error('隐式流程初始化失败:', err)
            this.setState({
              isIniting: false,
              isOccurError: true,
              errorMsg: formatErrorMsg(err)
            })
          })
        this.i18n = (i18n && i18n.default ? i18n.default : i18n)(this.options.language)
        return
      }
    }

    // 处理授权码流程（?code=...）
    const query = queryParse()
    if (query.code) {
      const code = query.code
      delete query.code
      const replacedUrl = `${window.location.origin}${window.location.pathname}?${queryStringify(query)}${window.location.hash}`
      history.replaceState(null, null, replacedUrl)
      this.options = Object.assign({}, this.options, {
        url: replacedUrl,
        id: replacedUrl
      }, props.options)

      const tokenUrl = this.options.proxy
        ? `${this.options.proxy}/access_token`
        : 'https://codeberg.org/login/oauth/access_token'

      const postData = this.options.proxy
        ? { code }
        : {
            code,
            client_id: this.options.clientID,
            client_secret: this.options.clientSecret
          }

      axiosJSON.post(tokenUrl, postData).then(res => {
        if (res.data && res.data.access_token) {
          this.accessToken = res.data.access_token

          this.getInit()
            .then(() => this.setState({ isIniting: false }))
            .catch(err => {
              console.log('err:', err)
              this.setState({
                isIniting: false,
                isOccurError: true,
                errorMsg: formatErrorMsg(err)
              })
            })
        } else {
          console.log('res.data err:', res.data)
          this.setState({
            isOccurError: true,
            errorMsg: formatErrorMsg(new Error('no access token'))
          })
        }
      }).catch(err => {
        console.log('err: ', err)
        this.setState({
          isOccurError: true,
          errorMsg: formatErrorMsg(err)
        })
      })
    } else {
      this.getInit()
        .then(() => this.setState({ isIniting: false }))
        .catch(err => {
          console.log('err:', err)
          this.setState({
            isIniting: false,
            isOccurError: true,
            errorMsg: formatErrorMsg(err)
          })
        })
    }

    this.i18n = (i18n && i18n.default ? i18n.default : i18n)(this.options.language)
  }

  componentDidUpdate () {
    this.commentEL && autosize(this.commentEL)
  }

  get accessToken () {
    return this._accessToke || window.localStorage.getItem(CT_ACCESS_TOKEN)
  }
  set accessToken (token) {
    window.localStorage.setItem(CT_ACCESS_TOKEN, token)
    this._accessToken = token
  }

  get loginLink () {
    const { clientID, proxy } = this.options
    const query = {
      client_id: clientID,
      redirect_uri: window.location.href,
      scope: 'public_repo'
    }
    if (proxy) {
      return `${proxy}/authorize?${queryStringify(query)}`
    }
    const codebergOauthUrl = 'https://codeberg.org/login/oauth/authorize'
    return `${codebergOauthUrl}?${queryStringify(query)}`
  }

  get isAdmin () {
    const { admin } = this.options
    const { user } = this.state
    return user && ~[].concat(admin).map(a => a.toLowerCase()).indexOf(user.login.toLowerCase())
  }

  getInit () {
    return this.getUserInfo().then(() => this.getIssue()).then(issue => this.getComments(issue))
  }

  getUserInfo () {
    if (!this.accessToken) {
      return new Promise(resolve => resolve())
    }
    return axiosCodeberg.get('/user', {
      headers: { Authorization: `token ${this.accessToken}` }
    }).then(res => {
      this.setState({ user: res.data })
    }).catch(() => this.logout())
  }

  getIssueById () {
    const { owner, repo, number } = this.options
    const getUrl = `/repos/${owner}/${repo}/issues/${number}`

    return new Promise((resolve, reject) => {
      axiosCodeberg.get(getUrl, { params: { t: Date.now() } })
        .then(res => {
          let issue = null
          if (res && res.data && res.data.number === number) {
            issue = res.data
            this.setState({ issue, isNoInit: false })
          }
          resolve(issue)
        })
        .catch(err => {
          if (err.response && err.response.status === 404) resolve(null)
          else reject(err)
        })
    })
  }

  // ✅ 改造：使用 body 内嵌标识 + q 参数搜索，不再依赖动态标签
  getIssueByLabels () {
    const { owner, repo, id, labels } = this.options

    // 搜索 body 中的页面唯一标识
    const queryParams = {
      labels: labels.join(','),          // 保持 'Cotalk' 标签过滤，缩小范围
      q: `"cotalk-id: ${id}"`,            // 精确搜索 body 中的页面标识
      state: 'all',                       // 所有状态（避免 Issue 被关闭后找不到）
      t: Date.now()
    }

    return axiosCodeberg.get(`/repos/${owner}/${repo}/issues`, {
      params: queryParams
    }).then(res => {
      const { createIssueManually } = this.options
      let isNoInit = false
      let issue = null
      if (!(res && res.data && res.data.length)) {
        if (!createIssueManually && this.isAdmin) {
          return this.createIssue()
        }
        isNoInit = true
      } else {
        issue = res.data[0]
      }
      this.setState({ issue, isNoInit })
      return issue
    })
  }

  getIssue () {
    const { number } = this.options
    const { issue } = this.state
    if (issue) {
      this.setState({ isNoInit: false })
      return Promise.resolve(issue)
    }

    if (typeof number === 'number' && number > 0) {
      return this.getIssueById().then(resIssue => {
        if (!resIssue) return this.getIssueByLabels()
        return resIssue
      })
    }
    return this.getIssueByLabels()
  }

  // ✅ 改造：创建 Issue 时 body 中嵌入页面标识，labels 仅使用已有的 'Cotalk' 标签
  createIssue () {
    const { owner, repo, title, body, id, labels, url } = this.options

    // 先获取已有标签 ID 映射（只关心 'Cotalk'）
    return axiosCodeberg.get(`/repos/${owner}/${repo}/labels`)
      .then(response => {
        const existingLabels = response.data
        const labelMap = new Map()
        existingLabels.forEach(label => {
          labelMap.set(label.name, label.id)
        })

        const cotalkLabelId = labelMap.get('Cotalk')
        const labelIds = cotalkLabelId !== undefined ? [cotalkLabelId] : []

        // body 中嵌入页面标识（HTML 注释，不影响渲染）
        const bodyWithId = `<!-- cotalk-id: ${id} -->\n${body || `${url} \n\n ${
          getMetaContent('description') ||
          getMetaContent('description', 'og:description') || ''
        }`}`

        return axiosCodeberg.post(`/repos/${owner}/${repo}/issues`, {
          title,
          labels: labelIds,
          body: bodyWithId
        }, {
          headers: {
            Authorization: `token ${this.accessToken}`
          }
        })
      })
      .then(res => {
        this.setState({ issue: res.data })
        return res.data
      })
      .catch(err => {
        console.error('创建 Issue 失败:', err.response?.data || err)
        throw err
      })
  }

  getCommentsV3 = issue => {
    const { perPage } = this.options
    const { page } = this.state

    return this.getIssue()
      .then(issue => {
        if (!issue) return
        const commentsUrl = issue.comments_url ||
          `https://codeberg.org/api/v1/repos/${issue.repository?.full_name || `${this.options.owner}/${this.options.repo}`}/issues/${issue.number}/comments`

        return axiosCodeberg.get(commentsUrl, {
          headers: { Accept: 'application/json' },
          params: { per_page: perPage, page }
        }).then(res => {
          const normalizedComments = res.data.map(comment => ({
            id: comment.id,
            user: {
              avatar_url: comment.user?.avatar_url || '',
              login: comment.user?.login || 'unknown',
              html_url: comment.user?.html_url || `https://codeberg.org/${comment.user?.login}`
            },
            created_at: comment.created_at,
            body: comment.body || '',
            body_html: comment.body_html || marked.parse(comment.body || ''),
            reactions: comment.reactions || { totalCount: 0, nodes: [], viewerHasReacted: false },
            html_url: comment.html_url ||
              `https://codeberg.org/${this.options.owner}/${this.options.repo}/issues/${issue.number}#issuecomment-${comment.id}`
          }))

          const { comments, issue } = this.state
          let isLoadOver = false
          const cs = comments.concat(normalizedComments)
          if (cs.length >= issue.comments || res.data.length < perPage) {
            isLoadOver = true
          }
          this.setState({
            comments: cs,
            isLoadOver,
            page: page + 1
          })
          return cs
        })
      })
  }

  getComments (issue) {
    if (!issue) return
    return this.getCommentsV3(issue)
  }

  createComment () {
    const { comment, localComments, comments } = this.state

    return this.getIssue()
      .then(issue => {
        const commentsUrl = issue.comments_url ||
          `https://codeberg.org/api/v1/repos/${issue.repository?.full_name || `${this.options.owner}/${this.options.repo}`}/issues/${issue.number}/comments`
        return axiosCodeberg.post(commentsUrl, { body: comment }, {
          headers: {
            Accept: 'application/json',
            Authorization: `token ${this.accessToken}`
          }
        })
      })
      .then(res => {
        const newComment = {
          ...res.data,
          body_html: res.data.body_html || marked.parse(res.data.body || ''),
          user: {
            avatar_url: res.data.user?.avatar_url || '',
            login: res.data.user?.login || this.state.user?.login || '',
            html_url: res.data.user?.html_url || `https://codeberg.org/${res.data.user?.login}`
          },
          reactions: res.data.reactions || { totalCount: 0, nodes: [], viewerHasReacted: false },
          html_url: res.data.html_url ||
            `https://codeberg.org/${this.options.owner}/${this.options.repo}/issues/${this.state.issue.number}#issuecomment-${res.data.id}`
        }
        this.setState({
          comment: '',
          comments: comments.concat(newComment),
          localComments: localComments.concat(newComment)
        })
      })
  }

  logout () {
    this.setState({ user: null })
    window.localStorage.removeItem(CT_ACCESS_TOKEN)
  }

  getRef = e => { this.publicBtnEL = e }

  reply = replyComment => () => {
    const { comment } = this.state
    const replyCommentBody = replyComment.body
    let replyCommentArray = replyCommentBody.split('\n')
    replyCommentArray.unshift(`@${replyComment.user.login}`)
    replyCommentArray = replyCommentArray.map(t => `> ${t}`)
    replyCommentArray.push('')
    replyCommentArray.push('')
    if (comment) replyCommentArray.unshift('')
    this.setState({ comment: comment + replyCommentArray.join('\n') }, () => {
      autosize.update(this.commentEL)
      this.commentEL.focus()
    })
  }

  like (comment) {
    const { owner, repo } = this.options
    const { user } = this.state
    let { comments } = this.state

    axiosCodeberg.post(`/repos/${owner}/${repo}/issues/comments/${comment.id}/reactions`, {
      content: 'heart'
    }, {
      headers: {
        Authorization: `token ${this.accessToken}`,
        Accept: 'application/json'
      }
    }).then(res => {
      comments = comments.map(c => {
        if (c.id === comment.id) {
          if (!c.reactions) c.reactions = { totalCount: 0, nodes: [] }
          c.reactions.totalCount += 1
          c.reactions.nodes.push(res.data)
          c.reactions.viewerHasReacted = true
          return Object.assign({}, c)
        }
        return c
      })
      this.setState({ comments })
    }).catch(() => {})
  }

  unLike (comment) {
    // Codeberg REST API 暂不支持取消点赞
  }

  handlePopup = e => {
    e.preventDefault()
    e.stopPropagation()
    const isVisible = !this.state.isPopupVisible
    const hideHandle = e1 => {
      if (hasClassInParent(e1.target, 'gt-user', 'gt-popup')) {
        return
      }
      window.document.removeEventListener('click', hideHandle)
      this.setState({ isPopupVisible: false })
    }
    this.setState({ isPopupVisible: isVisible })
    if (isVisible) {
      window.document.addEventListener('click', hideHandle)
    } else {
      window.document.removeEventListener('click', hideHandle)
    }
  }

  handleLogin = () => {
    const { comment } = this.state
    window.localStorage.setItem(CT_COMMENT, encodeURIComponent(comment))
    window.location.href = this.loginLink
  }

  handleIssueCreate = () => {
    this.setState({ isIssueCreating: true })
    this.createIssue().then(issue => {
      this.setState({ isIssueCreating: false, isOccurError: false })
      return this.getComments(issue)
    }).catch(err => {
      this.setState({ isIssueCreating: false, isOccurError: true, errorMsg: formatErrorMsg(err) })
    }).then(res => {
      if (res) this.setState({ isNoInit: false })
    })
  }

  handleCommentCreate = e => {
    if (!this.state.comment.length) {
      e && e.preventDefault()
      this.commentEL.focus()
      return
    }
    this.setState(state => {
      if (state.isCreating) return
      this.createComment()
        .then(() => this.setState({ isCreating: false, isOccurError: false }))
        .catch(err => this.setState({ isCreating: false, isOccurError: true, errorMsg: formatErrorMsg(err) }))
      return { isCreating: true }
    })
  }

  handleCommentPreview = e => {
    this.setState({ isPreview: !this.state.isPreview })
    if (!this.state.isPreview) return
    axiosCodeberg.post('/markdown', {
      text: this.state.comment
    }, {
      headers: this.accessToken && { Authorization: `token ${this.accessToken}` }
    }).then(res => {
      this.setState({ previewHtml: res.data })
    }).catch(err => {
      this.setState({ isOccurError: true, errorMsg: formatErrorMsg(err) })
    })
  }

  handleCommentLoad = () => {
    const { issue, isLoadMore } = this.state
    if (isLoadMore) return
    this.setState({ isLoadMore: true })
    this.getComments(issue).then(() => this.setState({ isLoadMore: false }))
  }

  handleCommentChange = e => this.setState({ comment: e.target.value })

  handleLogout = () => {
    this.logout()
    window.location.reload()
  }

  handleCommentFocus = e => {
    const { distractionFreeMode } = this.options
    if (!distractionFreeMode) return e.preventDefault()
    this.setState({ isInputFocused: true })
  }

  handleCommentBlur = e => {
    const { distractionFreeMode } = this.options
    if (!distractionFreeMode) return e.preventDefault()
    this.setState({ isInputFocused: false })
  }

  handleSort = direction => e => this.setState({ pagerDirection: direction })

  handleCommentKeyDown = e => {
    const { enableHotKey } = this.options
    if (enableHotKey && (e.metaKey || e.ctrlKey) && e.keyCode === 13) {
      this.publicBtnEL && this.publicBtnEL.focus()
      this.handleCommentCreate()
    }
  }

  initing () {
    return (
      <div className="gt-initing">
        <i className="gt-loader"/>
        <p className="gt-initing-text">{this.i18n.t('init')}</p>
      </div>
    )
  }

  noInit () {
    const { user, isIssueCreating } = this.state
    const { owner, repo, admin } = this.options
    return (
      <div className="gt-no-init" key="no-init">
        <p dangerouslySetInnerHTML={{
          __html: this.i18n.t('no-found-related', {
            link: `<a href="https://codeberg.org/${owner}/${repo}/issues">Issues</a>`
          })
        }}/>
        <p>{this.i18n.t('please-contact', { user: [].concat(admin).map(u => `@${u}`).join(' ') })}</p>
        {this.isAdmin ? <p>
          <Button onClick={this.handleIssueCreate} isLoading={isIssueCreating} text={this.i18n.t('init-issue')} />
        </p> : null}
        {!user && <Button className="gt-btn-login" onClick={this.handleLogin} text={this.i18n.t('login-with-codeberg')} />}
      </div>
    )
  }

  header () {
    const { user, comment, isCreating, previewHtml, isPreview } = this.state
    return (
      <div className="gt-header" key="header">
        {user ?
          <Avatar className="gt-header-avatar" src={user.avatar_url} alt={user.login} /> :
          <a className="gt-avatar-codeberg" onClick={this.handleLogin}>
            <Svg className="gt-ico-codeberg" name="codeberg"/>
          </a>
        }
        <div className="gt-header-comment">
          <textarea
            ref={t => { this.commentEL = t }}
            className={`gt-header-textarea ${isPreview ? 'hide' : ''}`}
            value={comment}
            onChange={this.handleCommentChange}
            onFocus={this.handleCommentFocus}
            onBlur={this.handleCommentBlur}
            onKeyDown={this.handleCommentKeyDown}
            placeholder={this.i18n.t('leave-a-comment')}
          />
          <div
            className={`gt-header-preview markdown-body ${isPreview ? '' : 'hide'}`}
            dangerouslySetInnerHTML={{ __html: previewHtml }}
          />
          <div className="gt-header-controls">
            <a className="gt-header-controls-tip" href="https://docs.codeberg.org/markdown/" target="_blank" rel="noopener noreferrer">
              <Svg className="gt-ico-tip" name="tip" text={this.i18n.t('support-markdown')}/>
            </a>
            {user && <Button
              getRef={this.getRef}
              className="gt-btn-public"
              onClick={this.handleCommentCreate}
              text={this.i18n.t('comment')}
              isLoading={isCreating}
            />}
            <Button
              className="gt-btn-preview"
              onClick={this.handleCommentPreview}
              text={isPreview ? this.i18n.t('edit') : this.i18n.t('preview')}
            />
            {!user && <Button className="gt-btn-login" onClick={this.handleLogin} text={this.i18n.t('login-with-codeberg')} />}
          </div>
        </div>
      </div>
    )
  }

  comments () {
    const { user, comments, isLoadOver, isLoadMore, pagerDirection } = this.state
    const { language, flipMoveOptions, admin } = this.options
    const totalComments = comments.concat([])
    if (pagerDirection === 'last' && this.accessToken) {
      totalComments.reverse()
    }
    return (
      <div className="gt-comments" key="comments">
        <FlipMove {...flipMoveOptions}>
          {totalComments.map(c => (
            <Comment
              comment={c}
              key={c.id}
              user={user}
              language={language}
              commentedText={this.i18n.t('commented')}
              admin={admin}
              replyCallback={this.reply(c)}
              likeCallback={c.reactions && c.reactions.viewerHasReacted ? this.unLike.bind(this, c) : this.like.bind(this, c)}
            />
          ))}
        </FlipMove>
        {!totalComments.length && <p className="gt-comments-null">{this.i18n.t('first-comment-person')}</p>}
        {(!isLoadOver && totalComments.length) ? <div className="gt-comments-controls">
          <Button className="gt-btn-loadmore" onClick={this.handleCommentLoad} isLoading={isLoadMore} text={this.i18n.t('load-more')} />
        </div> : null}
      </div>
    )
  }

  meta () {
    const { user, issue, isPopupVisible, pagerDirection, localComments } = this.state
    const cnt = (issue && issue.comments) + localComments.length
    const isDesc = pagerDirection === 'last'
    const { updateCountCallback } = this.options

    if (
      updateCountCallback &&
      {}.toString.call(updateCountCallback) === '[object Function]'
    ) {
      try {
        updateCountCallback(cnt)
      } catch (err) {
        console.log('An error occurred executing the updateCountCallback:', err)
      }
    }

    return (
      <div className="gt-meta" key="meta" >
        <span className="gt-counts" dangerouslySetInnerHTML={{
          __html: this.i18n.t('counts', {
            counts: `<a class="gt-link gt-link-counts" href="${issue && issue.html_url}" target="_blank" rel="noopener noreferrer">${cnt}</a>`,
            smart_count: cnt
          })
        }}/>
        {isPopupVisible &&
          <div className="gt-popup">
            {user ? <Action className={`gt-action-sortasc${!isDesc ? ' is--active' : ''}`} onClick={this.handleSort('first')} text={this.i18n.t('sort-asc')}/> : null }
            {user ? <Action className={`gt-action-sortdesc${isDesc ? ' is--active' : ''}`} onClick={this.handleSort('last')} text={this.i18n.t('sort-desc')}/> : null }
            {user ?
              <Action className="gt-action-logout" onClick={this.handleLogout} text={this.i18n.t('logout')}/> :
              <a className="gt-action gt-action-login" onClick={this.handleLogin}>{this.i18n.t('login-with-codeberg')}</a>
            }
            <div className="gt-copyright">
              <a className="gt-link gt-link-project" href="https://codeberg.org/cotalk/cotalk" target="_blank" rel="noopener noreferrer">Cotalk</a>
              <span className="gt-version">{CT_VERSION}</span>
            </div>
          </div>
        }
        <div className="gt-user">
          {user ?
            <div className={isPopupVisible ? 'gt-user-inner is--poping' : 'gt-user-inner'} onClick={this.handlePopup}>
              <span className="gt-user-name">{user.login}</span>
              <Svg className="gt-ico-arrdown" name="arrow_down"/>
            </div> :
            <div className={isPopupVisible ? 'gt-user-inner is--poping' : 'gt-user-inner'} onClick={this.handlePopup}>
              <span className="gt-user-name">{this.i18n.t('anonymous')}</span>
              <Svg className="gt-ico-arrdown" name="arrow_down"/>
            </div>
          }
        </div>
      </div>
    )
  }

  render () {
    const { isIniting, isNoInit, isOccurError, errorMsg, isInputFocused } = this.state
    return (
      <div className={`gt-container${isInputFocused ? ' gt-input-focused' : ''}`}>
        {isIniting && this.initing()}
        {!isIniting && (
          isNoInit ? [
          ] : [
            this.meta()
          ])
        }
        {isOccurError && <div className="gt-error">
          {errorMsg}
        </div>}
        {!isIniting && (
          isNoInit ? [
            this.noInit()
          ] : [
            this.header(),
            this.comments()
          ])
        }
      </div>
    )
  }
}

export default CotalkComponent