/**
 * Directus API Service for dgray.io
 * Connects to https://api.dgray.io/
 * @module services/directus
 */

/** @type {string} */
const DIRECTUS_URL = 'https://api.dgray.io'

/**
 * @typedef {Object} AuthTokens
 * @property {string} access_token - JWT access token
 * @property {string} refresh_token - Refresh token for session renewal
 * @property {number} expires - Token expiry in seconds
 */

/**
 * @typedef {Object} Listing
 * @property {string} id - UUID
 * @property {string} title - Listing title
 * @property {string} [description] - Listing description
 * @property {number} price - Price value
 * @property {string} currency - Currency code (EUR, USD, CHF, XMR)
 * @property {string} status - Status (draft, published, sold, expired)
 * @property {Object} [location] - Location object
 * @property {Array} [images] - Array of image objects
 */

/**
 * @typedef {Object} Category
 * @property {string} id - UUID
 * @property {string} slug - URL-safe slug
 * @property {string} [parent] - Parent category ID
 * @property {Array} [translations] - Category translations
 */

/**
 * Main Directus API client
 * @class
 */
class DirectusService {
    constructor() {
        this.baseUrl = DIRECTUS_URL
        this.accessToken = null
        this.refreshToken = null
        this.tokenExpiry = null
        this.refreshTimeout = 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
        
        // Refresh 1 minute before expiry
        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)
    }

    // ==================== HTTP Methods ====================

    /**
     * Make an HTTP request to the Directus API
     * @param {string} endpoint - API endpoint path
     * @param {RequestInit} [options={}] - Fetch options
     * @returns {Promise<Object|null>} Response data or null for 204
     * @throws {DirectusError} On request failure
     */
    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
            })
            
            // Token expired - try refresh (but not for auth endpoints)
            if (response.status === 401 && this.refreshToken && !endpoint.startsWith('/auth/')) {
                const refreshed = await this.refreshSession()
                if (refreshed) {
                    return this.request(endpoint, options)
                } else {
                    this.clearTokens()
                    return this.request(endpoint, options)
                }
            }

            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 })
        }
    }

    /**
     * GET request with query parameters
     * @param {string} endpoint - API endpoint
     * @param {Object} [params={}] - Query parameters
     * @returns {Promise<Object>} Response data
     */
    async get(endpoint, params = {}) {
        const queryString = this.buildQueryString(params)
        const url = queryString ? `${endpoint}?${queryString}` : endpoint
        return this.request(url, { method: 'GET' })
    }

    /**
     * POST request with JSON body
     * @param {string} endpoint - API endpoint
     * @param {Object} data - Request body
     * @returns {Promise<Object>} Response data
     */
    async post(endpoint, data) {
        return this.request(endpoint, {
            method: 'POST',
            body: JSON.stringify(data)
        })
    }

    /**
     * PATCH request with JSON body
     * @param {string} endpoint - API endpoint
     * @param {Object} data - Request body
     * @returns {Promise<Object>} Response 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()
    }

    // ==================== Authentication ====================

    async login(email, password) {
        const response = await this.post('/auth/login', { email, password })
        
        if (response.data) {
            this.saveTokens(
                response.data.access_token,
                response.data.refresh_token,
                response.data.expires
            )
        }
        
        return response.data
    }

    async logout() {
        if (this.refreshToken) {
            try {
                await this.post('/auth/logout', { refresh_token: this.refreshToken })
            } catch (e) {
                // Ignore errors - tokens will be cleared anyway
            }
        }
        this.clearTokens()
    }

    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
    }

    async register(email, password) {
        // Public registration (no verification required)
        return this.post('/users/register', { email, password })
    }

    async requestPasswordReset(email) {
        return this.post('/auth/password/request', { email })
    }

    async resetPassword(token, password) {
        return this.post('/auth/password/reset', { token, password })
    }

    async getCurrentUser() {
        const response = await this.get('/users/me', {
            fields: ['id', 'email', 'first_name', 'last_name', 'avatar', 'role.name', 'status', 'preferred_currency', 'preferred_locale']
        })
        return response.data
    }

    async updateCurrentUser(data) {
        const response = await this.patch('/users/me', data)
        return response.data
    }

    // ==================== Listings (Anzeigen) ====================

    /**
     * Get listings with filters and pagination
     * @param {Object} [options={}] - Query options
     * @param {number} [options.limit=20] - Max items to return
     * @param {number} [options.page=1] - Page number
     * @param {string} [options.category] - Filter by category slug
     * @param {string} [options.search] - Search query
     * @returns {Promise<{items: Listing[], total: number}>} Paginated listings
     */
    async getListings(options = {}) {
        const params = {
            fields: options.fields || [
                'id',
                'status',
                'title',
                'slug',
                'price',
                'currency',
                'condition',
                'expires_at',
                'date_created',
                'user_created',
                'images.directus_files_id.id',
                'category.id',
                'category.name',
                'category.slug',
                'category.icon',
                'location.id',
                'location.name',
                'location.postal_code',
                'location.country',
                'location.latitude',
                'location.longitude'
            ],
            filter: options.filter || {
                status: { _eq: 'published' },
                _or: [
                    { expires_at: { _null: true } },
                    { expires_at: { _gt: '$NOW' } }
                ]
            },
            sort: options.sort || ['-date_created'],
            limit: options.limit || 20,
            page: options.page || 1
        }
        
        if (options.search) {
            params.search = options.search
        }
        
        const response = await this.get('/items/listings', params)
        return {
            items: response.data,
            meta: response.meta
        }
    }

    /**
     * Get a single listing by ID
     * @param {string} id - Listing UUID
     * @returns {Promise<Listing|null>} Listing object or null
     */
    async getListing(id) {
        const response = await this.get(`/items/listings/${id}`, {
            fields: [
                'id',
                'status',
                'title',
                'slug',
                'description',
                'price',
                'currency',
                'price_mode',
                'price_type',
                'condition',
                'shipping',
                'shipping_cost',
                'views',
                'paid_at',
                'payment_status',
                'expires_at',
                'date_created',
                'user_created',
                'images.directus_files_id.id',
                'category.id',
                'category.name',
                'category.slug',
                'category.translations.*',
                'location.id',
                'location.name',
                'location.postal_code',
                'location.country'
            ]
        })
        return response.data
    }

    /**
     * Create a new listing
     * @param {Object} data - Listing data
     * @returns {Promise<Listing>} Created listing
     */
    async createListing(data) {
        const response = await this.post('/items/listings', data)
        return response?.data || response
    }

    async updateListing(id, data) {
        const response = await this.patch(`/items/listings/${id}`, data)
        return response.data
    }

    async incrementListingViews(id) {
        // Check if user already viewed this listing (session-based)
        const viewedKey = `dgray_viewed_${id}`
        if (sessionStorage.getItem(viewedKey)) {
            return null
        }

        try {
            // Get current views
            const listing = await this.get(`/items/listings/${id}`, {
                fields: ['views']
            })
            const currentViews = listing.data?.views || 0
            const newViews = currentViews + 1

            // Increment views
            await this.patch(`/items/listings/${id}`, {
                views: newViews
            })

            // Mark as viewed for this session
            sessionStorage.setItem(viewedKey, 'true')
            return newViews
        } catch (error) {
            console.error('Failed to increment views:', error)
            return null
        }
    }

    async deleteListing(id) {
        return this.delete(`/items/listings/${id}`)
    }

    async getMyListings() {
        const response = await this.get('/items/listings', {
            fields: ['*', 'images.directus_files_id.id', 'category.id', 'category.name', 'location.name'],
            filter: { user_created: { _eq: '$CURRENT_USER' } },
            sort: ['-date_created']
        })
        return response.data || []
    }

    async searchListings(query, options = {}) {
        return this.getListings({
            search: query,
            ...options
        })
    }

    async getListingsByCategory(categoryId, options = {}) {
        return this.getListings({
            filter: {
                status: { _eq: 'published' },
                category: { _eq: categoryId }
            },
            ...options
        })
    }

    async getListingsByLocation(locationId, options = {}) {
        return this.getListings({
            filter: {
                status: { _eq: 'published' },
                location: { _eq: locationId }
            },
            ...options
        })
    }

    // ==================== Categories (Kategorien) ====================

    async getCategories() {
        const response = await this.get('/items/categories', {
            fields: ['*', 'translations.*'],
            filter: { status: { _eq: 'published' } },
            sort: ['sort', 'name'],
            limit: -1
        })
        return response.data || []
    }

    async getCategory(idOrSlug) {
        const isUuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(idOrSlug)
        
        if (isUuid) {
            const response = await this.get(`/items/categories/${idOrSlug}`, {
                fields: ['*', 'translations.*', 'parent.*']
            })
            return response.data
        }
        
        const response = await this.get('/items/categories', {
            fields: ['*', 'translations.*', 'parent.*'],
            filter: { slug: { _eq: idOrSlug } },
            limit: 1
        })
        return response.data?.[0] || null
    }

    async getCategoryTree() {
        const categories = await this.getCategories()
        return this.buildCategoryTree(categories)
    }

    buildCategoryTree(categories, parentId = null) {
        return categories
            .filter(cat => (cat.parent?.id || cat.parent) === parentId)
            .map(cat => ({
                ...cat,
                children: this.buildCategoryTree(categories, cat.id)
            }))
    }

    async getSubcategories(parentId) {
        const response = await this.get('/items/categories', {
            fields: ['*', 'translations.*'],
            filter: {
                status: { _eq: 'published' },
                parent: { _eq: parentId }
            },
            sort: ['sort', 'name']
        })
        return response.data
    }

    // ==================== Locations (Standorte) ====================

    async getLocations(options = {}) {
        const response = await this.get('/items/locations', {
            fields: options.fields || ['*'],
            filter: options.filter || {},
            sort: options.sort || ['name'],
            limit: options.limit || -1
        })
        return response.data
    }

    async getLocation(id) {
        const response = await this.get(`/items/locations/${id}`)
        return response.data
    }

    async searchLocations(query) {
        const response = await this.get('/items/locations', {
            search: query,
            limit: 20
        })
        return response.data
    }

    async getLocationsByRegion(region) {
        const response = await this.get('/items/locations', {
            filter: { region: { _eq: region } },
            sort: ['name']
        })
        return response.data
    }

    async getLocationsByCountry(country) {
        const response = await this.get('/items/locations', {
            filter: { country: { _eq: country } },
            sort: ['region', 'name']
        })
        return response.data
    }

    // ==================== Conversations (Zero-Knowledge Chat) ====================

    async getConversations(participantHash) {
        const response = await this.get('/items/conversations', {
            fields: [
                '*',
                'listing_id.id',
                'listing_id.title',
                'listing_id.images.directus_files_id.id'
            ],
            filter: {
                _or: [
                    { participant_hash_1: { _eq: participantHash } },
                    { participant_hash_2: { _eq: participantHash } }
                ]
            },
            sort: ['-date_updated']
        })
        return response.data
    }

    async getConversation(id) {
        const response = await this.get(`/items/conversations/${id}`, {
            fields: [
                '*',
                'listing_id.*',
                'listing_id.images.directus_files_id.*'
            ]
        })
        return response.data
    }

    async getConversationMessages(conversationId) {
        const response = await this.get('/items/messages', {
            fields: ['*'],
            filter: { conversation: { _eq: conversationId } },
            sort: ['date_created']
        })
        return response.data
    }

    async sendMessage(conversationId, senderHash, encryptedContent, nonce, type = 'text') {
        const response = await this.post('/items/messages', {
            conversation: conversationId,
            sender_hash: senderHash,
            content_encrypted: encryptedContent,
            nonce: nonce,
            type: type
        })
        return response.data
    }

    async startConversation(listingId, participantHash1, participantHash2, publicKey1, publicKey2) {
        const response = await this.post('/items/conversations', {
            listing_id: listingId,
            participant_hash_1: participantHash1,
            participant_hash_2: participantHash2,
            public_key_1: publicKey1,
            public_key_2: publicKey2,
            status: 'active'
        })
        return response.data
    }

    async findConversation(listingId, participantHash1, participantHash2) {
        const response = await this.get('/items/conversations', {
            filter: {
                listing_id: { _eq: listingId },
                _or: [
                    {
                        _and: [
                            { participant_hash_1: { _eq: participantHash1 } },
                            { participant_hash_2: { _eq: participantHash2 } }
                        ]
                    },
                    {
                        _and: [
                            { participant_hash_1: { _eq: participantHash2 } },
                            { participant_hash_2: { _eq: participantHash1 } }
                        ]
                    }
                ]
            },
            limit: 1
        })
        return response.data?.[0] || null
    }

    async updateConversationStatus(id, status) {
        const response = await this.patch(`/items/conversations/${id}`, { status })
        return response.data
    }

    // ==================== Files (Dateien/Bilder) ====================

    /**
     * Upload a file to Directus
     * @param {File} file - File to upload
     * @param {Object} [options={}] - Upload options
     * @param {string} [options.folder] - Target folder ID
     * @returns {Promise<Object>} Uploaded file data
     */
    async uploadFile(file, options = {}) {
        const formData = new FormData()
        formData.append('file', file)
        
        if (options.folder) {
            formData.append('folder', options.folder)
        }
        
        const response = await fetch(`${this.baseUrl}/files`, {
            method: 'POST',
            headers: this.accessToken ? { 'Authorization': `Bearer ${this.accessToken}` } : {},
            body: formData
        })
        
        if (!response.ok) {
            const error = await response.json().catch(() => ({}))
            throw new DirectusError(response.status, 'Upload failed', error)
        }
        
        const result = await response.json()
        return result.data
    }

    async uploadMultipleFiles(files, options = {}) {
        const uploads = Array.from(files).map(file => this.uploadFile(file, options))
        return Promise.all(uploads)
    }

    /**
     * Get URL for a file with optional transformations
     * @param {string} fileId - File UUID
     * @param {Object} [options={}] - Transform options
     * @param {number} [options.width] - Resize width
     * @param {number} [options.height] - Resize height
     * @param {string} [options.fit] - Fit mode (cover, contain, etc.)
     * @returns {string|null} File URL or null
     */
    getFileUrl(fileId, options = {}) {
        if (!fileId) return null
        
        const params = new URLSearchParams()
        
        if (options.width) params.set('width', options.width)
        if (options.height) params.set('height', options.height)
        if (options.fit) params.set('fit', options.fit)
        if (options.quality) params.set('quality', options.quality)
        if (options.format) params.set('format', options.format)
        
        const queryString = params.toString()
        return `${this.baseUrl}/assets/${fileId}${queryString ? '?' + queryString : ''}`
    }

    // ── Notifications ──

    async getNotifications(userHash, options = {}) {
        const params = {
            fields: ['id', 'type', 'reference_id', 'read', 'date_created', 'user_hash'],
            filter: { user_hash: { _eq: userHash } },
            sort: ['-date_created'],
            limit: options.limit || 50
        }
        if (options.unreadOnly) {
            params.filter.read = { _eq: false }
        }
        const response = await this.get('/items/notifications', params)
        return response.data
    }

    async getUnreadCount(userHash) {
        const response = await this.get('/items/notifications', {
            filter: { user_hash: { _eq: userHash }, read: { _eq: false } },
            aggregate: { count: 'id' }
        })
        return parseInt(response.data?.[0]?.count?.id || '0', 10)
    }

    async markNotificationRead(id) {
        return this.patch(`/items/notifications/${id}`, { read: true })
    }

    async markAllNotificationsRead(userHash) {
        const unread = await this.getNotifications(userHash, { unreadOnly: true })
        const updates = unread.map(n => this.markNotificationRead(n.id))
        return Promise.all(updates)
    }

    async deleteNotification(id) {
        return this.delete(`/items/notifications/${id}`)
    }

    getThumbnailUrl(fileId, size = 300) {
        return this.getFileUrl(fileId, { width: size, height: size, fit: 'cover' })
    }

}

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
    }
}

// Singleton Export
export const directus = new DirectusService()
export { DirectusError }
