const DIRECTUS_URL = 'https://api.dgray.io'

class DirectusError extends Error {
    constructor(status, message, data = {}) {
        super(message)
        this.name = 'DirectusError'
        this.status = status
        this.data = data
    }

    isAuthError() {
        return this.status === 401 || this.status === 403
    }

    isNotFound() {
        return this.status === 404
    }

    isValidationError() {
        return this.status === 400
    }
}

class DirectusClient {
    constructor() {
        this.baseUrl = DIRECTUS_URL
        this.accessToken = null
        this.refreshToken = null
        this.tokenExpiry = null
        this.refreshTimeout = null
        this._refreshPromise = null

        this.loadTokens()
        this.setupVisibilityRefresh()
    }

    // ── Token Management ──

    loadTokens() {
        const stored = localStorage.getItem('dgray_auth')
        if (stored) {
            try {
                const { accessToken, refreshToken, expiry } = JSON.parse(stored)
                this.accessToken = accessToken
                this.refreshToken = refreshToken
                this.tokenExpiry = expiry
                this.scheduleTokenRefresh()
            } catch (e) {
                this.clearTokens()
            }
        }
    }

    saveTokens(accessToken, refreshToken, expiresIn) {
        this.accessToken = accessToken
        this.refreshToken = refreshToken
        this.tokenExpiry = Date.now() + (expiresIn * 1000)

        localStorage.setItem('dgray_auth', JSON.stringify({
            accessToken: this.accessToken,
            refreshToken: this.refreshToken,
            expiry: this.tokenExpiry
        }))

        this.scheduleTokenRefresh()
    }

    clearTokens() {
        this.accessToken = null
        this.refreshToken = null
        this.tokenExpiry = null
        localStorage.removeItem('dgray_auth')

        if (this.refreshTimeout) {
            clearTimeout(this.refreshTimeout)
            this.refreshTimeout = null
        }
    }

    scheduleTokenRefresh() {
        if (this.refreshTimeout) {
            clearTimeout(this.refreshTimeout)
        }

        if (!this.tokenExpiry || !this.refreshToken) return

        const refreshIn = this.tokenExpiry - Date.now() - 60000

        if (refreshIn > 0) {
            this.refreshTimeout = setTimeout(() => this.refreshSession(), refreshIn)
        } else if (this.refreshToken) {
            this.refreshSession()
        }
    }

    setupVisibilityRefresh() {
        document.addEventListener('visibilitychange', () => {
            if (document.visibilityState === 'visible' && this.refreshToken) {
                const timeLeft = this.tokenExpiry - Date.now()
                if (timeLeft < 120000) {
                    this.refreshSession()
                }
                this.scheduleTokenRefresh()
            }
        })
    }

    isAuthenticated() {
        return !!this.accessToken && (!this.tokenExpiry || Date.now() < this.tokenExpiry)
    }

    async refreshSession(_retryCount = 0) {
        if (!this.refreshToken) return false

        try {
            const response = await fetch(`${this.baseUrl}/auth/refresh`, {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({
                    refresh_token: this.refreshToken,
                    mode: 'json'
                })
            })

            if (!response.ok) throw new Error('Refresh failed')

            const result = await response.json()

            if (result.data) {
                this.saveTokens(
                    result.data.access_token,
                    result.data.refresh_token,
                    result.data.expires
                )
                return true
            }
        } catch (e) {
            if (_retryCount < 2) {
                await new Promise(r => setTimeout(r, 2000))
                return this.refreshSession(_retryCount + 1)
            }
            this.clearTokens()
        }

        return false
    }

    // ── HTTP Methods ──

    async request(endpoint, options = {}, _retryCount = 0) {
        const url = `${this.baseUrl}${endpoint}`

        const headers = {
            'Content-Type': 'application/json',
            ...options.headers
        }

        if (this.accessToken) {
            headers['Authorization'] = `Bearer ${this.accessToken}`
        }

        try {
            const response = await fetch(url, {
                ...options,
                headers
            })

            if (response.status === 401 && this.refreshToken && !endpoint.startsWith('/auth/') && _retryCount < 1) {
                if (!this._refreshPromise) {
                    this._refreshPromise = this.refreshSession().finally(() => {
                        this._refreshPromise = null
                    })
                }
                const refreshed = await this._refreshPromise
                if (!refreshed) {
                    this.clearTokens()
                }
                return this.request(endpoint, options, _retryCount + 1)
            }

            if (response.status === 429 && _retryCount < 3) {
                const retryAfterHeader = response.headers.get('Retry-After') || '1'
                let waitMs
                if (parseInt(retryAfterHeader, 10) > 100) {
                    waitMs = parseInt(retryAfterHeader, 10)
                } else {
                    waitMs = parseInt(retryAfterHeader, 10) * 1000
                }
                await new Promise(r => setTimeout(r, waitMs))
                return this.request(endpoint, options, _retryCount + 1)
            }

            if (!response.ok) {
                const error = await response.json().catch(() => ({}))
                throw new DirectusError(response.status, error.errors?.[0]?.message || 'Request failed', error)
            }

            if (response.status === 204) {
                return null
            }

            return await response.json()
        } catch (error) {
            if (error instanceof DirectusError) throw error
            throw new DirectusError(0, 'Network error', { originalError: error })
        }
    }

    async get(endpoint, params = {}) {
        const queryString = this.buildQueryString(params)
        const url = queryString ? `${endpoint}?${queryString}` : endpoint
        return this.request(url, { method: 'GET' })
    }

    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        })
    }

    async patch(endpoint, data) {
        return this.request(endpoint, {
            method: 'PATCH',
            body: JSON.stringify(data)
        })
    }

    async delete(endpoint) {
        return this.request(endpoint, { method: 'DELETE' })
    }

    buildQueryString(params) {
        const searchParams = new URLSearchParams()

        for (const [key, value] of Object.entries(params)) {
            if (value === undefined || value === null) continue

            if (key === 'sort' && Array.isArray(value)) {
                searchParams.set(key, value.join(','))
            } else if (key === 'fields' && Array.isArray(value)) {
                searchParams.set(key, value.join(','))
            } else if (typeof value === 'object') {
                searchParams.set(key, JSON.stringify(value))
            } else {
                searchParams.set(key, value)
            }
        }

        return searchParams.toString()
    }
}

export const client = new DirectusClient()
export { DirectusError }
