import { useAuthStore } from '@/stores/auth'
import { storeToRefs } from 'pinia'
import { PersistenceService } from './PersistenceService'
import { SessionExpiredError } from '@/common/errors/SessionExpiredError'

interface RequestOptions {
  uri: string
  params?: Record<string, string>
  body?: any
  auth?: boolean
  method?: string
}

export class NetworkService {
  private API_URL = import.meta.env.VITE_API_URL
  private auth = useAuthStore()
  private persistanceService: PersistenceService = PersistenceService.getInstance()

  async post(options: RequestOptions) {
    options.method = 'POST'
    return await this.request(options)
  }

  async get(options: RequestOptions) {
    options.method = 'GET'
    return await this.request(options)
  }

  async put(options: RequestOptions) {
    options.method = 'PUT'
    return await this.request(options)
  }

  async patch(options: RequestOptions) {
    options.method = 'PATCH'
    return await this.request(options)
  }

  async delete(options: RequestOptions) {
    options.method = 'DELETE'
    return await this.request(options)
  }

  async request(options: RequestOptions) {
    const { uri, params, auth } = options
    if (!uri) {
      throw new Error('URL is required')
    }
    const fetchOptions = this.getFetchOptions(options)
    const urlParams = this.getURLParams(params)
    let response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)

    const text = await response.text()
    if (!response.ok) {
      if (auth && response.status === 401) {
        const newAccessToken = await this.refresh()
        if (newAccessToken) {
          fetchOptions.headers.Authorization = newAccessToken
          this.persistanceService.saveToken(newAccessToken)
          response = await fetch(`${this.API_URL}${uri}${urlParams}`, fetchOptions)
        }
      } else {
        throw new Error('Network response was not ok')
      }
    }

    if (text === '') {
      return
    } else {
      return JSON.parse(text)
    }
  }

  async refresh() {
    const { refreshToken } = storeToRefs(this.auth)
    const { setToken } = this.auth
    try {
      const fetchResponse = await fetch(`${this.API_URL}/refresh`, {
        method: 'POST',
        headers: this.getHeaders({ auth: false }),
        body: JSON.stringify({ refreshToken: refreshToken.value }),
      })
      if (!fetchResponse.ok) {
        throw new SessionExpiredError()
      }
      const response = await fetchResponse.json()
      const { token } = response
      setToken(token)
      return token
    } catch (error) {
      throw new SessionExpiredError()
    }
  }

  getURLParams(params: any) {
    if (!params) {
      return ''
    }
    const urlParams = new URLSearchParams()
    Object.keys(params).forEach((key) => urlParams.append(key, params[key]))
    return `?${urlParams.toString()}`
  }

  getFetchOptions(opts: RequestOptions): any {
    const { body, auth, method = 'GET' } = opts
    const options: any = {
      method,
      headers: this.getHeaders({ auth }),
    }
    if (!['GET', 'HEAD'].includes(method) && body) {
      options.body = typeof body === 'string' ? body : JSON.stringify(body)
    }

    return options
  }

  getHeaders({ auth = true }): any {
    const { jwt } = storeToRefs(this.auth)
    const headers: any = {
      'Content-Type': 'application/json',
    }
    if (auth) {
      headers.Authorization = jwt.value
    }
    return headers
  }
}
