/**
 * Conversations Service - Zero-Knowledge Chat Implementation
 * Handles encrypted messaging between users using participant hashes
 */

import { directus } from './directus.js'
import { cryptoService } from './crypto.js'

class ConversationsService {
    constructor() {
        this.pollingInterval = null
        this.subscribers = new Set()
    }

    async getParticipantHash() {
        await cryptoService.ready
        const publicKey = cryptoService.getPublicKey()
        return this.hashPublicKey(publicKey)
    }

    async hashPublicKey(publicKey) {
        const encoder = new TextEncoder()
        const data = encoder.encode(publicKey)
        const hash = await crypto.subtle.digest('SHA-256', data)
        return Array.from(new Uint8Array(hash))
            .map(b => b.toString(16).padStart(2, '0'))
            .join('')
    }

    async getMyConversations() {
        const participantHash = await this.getParticipantHash()
        const conversations = await directus.getConversations(participantHash)

        const listingKeyHashes = await this.getMyListingKeyHashes()
        const allHashes = new Set([participantHash, ...listingKeyHashes])

        const sellerConvs = await this.getSellerConversations(listingKeyHashes)
        const allConvIds = new Set(conversations.map(c => c.id))
        for (const conv of sellerConvs) {
            if (!allConvIds.has(conv.id)) {
                conversations.push(conv)
                allConvIds.add(conv.id)
            }
        }
        
        conversations.sort((a, b) => {
            const da = new Date(b.date_updated || b.date_created)
            const db = new Date(a.date_updated || a.date_created)
            return da - db
        })

        return conversations.map(conv => {
            const isP1 = allHashes.has(conv.participant_hash_1)
            return {
                ...conv,
                isParticipant1: isP1,
                otherParticipantHash: isP1 
                    ? conv.participant_hash_2 
                    : conv.participant_hash_1,
                otherPublicKey: isP1
                    ? conv.public_key_2
                    : conv.public_key_1
            }
        })
    }

    async getMyListingKeyHashes() {
        if (this._listingKeyHashesCache) return this._listingKeyHashesCache

        const store = await cryptoService.getListingKeysStore()
        const hashes = []
        for (const [, secretKeyB64] of Object.entries(store)) {
            const sk = cryptoService.naclUtil.decodeBase64(secretKeyB64)
            const kp = cryptoService.nacl.box.keyPair.fromSecretKey(sk)
            const pubB64 = cryptoService.naclUtil.encodeBase64(kp.publicKey)
            const hash = await this.hashPublicKey(pubB64)
            hashes.push(hash)
        }
        this._listingKeyHashesCache = hashes
        setTimeout(() => { this._listingKeyHashesCache = null }, 30000)
        return hashes
    }

    async getSellerConversations(listingKeyHashes) {
        if (listingKeyHashes.length === 0) return []
        const response = await directus.get('/items/conversations', {
            fields: [
                '*',
                'listing_id.id',
                'listing_id.title',
                'listing_id.status',
                'listing_id.images.directus_files_id.id'
            ],
            filter: {
                _or: listingKeyHashes.map(h => ({
                    participant_hash_2: { _eq: h }
                }))
            },
            sort: ['-date_updated']
        })
        return response.data || []
    }

    async getConversation(id) {
        const conversation = await directus.getConversation(id)
        if (!conversation) return null

        const participantHash = await this.getParticipantHash()
        const listingKeyHashes = await this.getMyListingKeyHashes()
        const allHashes = new Set([participantHash, ...listingKeyHashes])
        const isP1 = allHashes.has(conversation.participant_hash_1)
        
        return {
            ...conversation,
            isParticipant1: isP1,
            otherParticipantHash: isP1 
                ? conversation.participant_hash_2 
                : conversation.participant_hash_1,
            otherPublicKey: isP1
                ? conversation.public_key_2
                : conversation.public_key_1,
            myPublicKey: isP1
                ? conversation.public_key_1
                : conversation.public_key_2
        }
    }

    async getMessages(conversationId, otherPublicKey, mySecretKey) {
        await cryptoService.ready

        if (!otherPublicKey) return []
        
        const messages = await directus.getConversationMessages(conversationId)
        const myHash = await this.getParticipantHash()
        const listingKeyHashes = await this.getMyListingKeyHashes()
        const allMyHashes = new Set([myHash, ...listingKeyHashes])
        
        return messages.map(msg => {
            const isOwn = allMyHashes.has(msg.sender_hash)
            let text = '[Encrypted]'
            
            try {
                text = cryptoService.decrypt(msg.content_encrypted, msg.nonce, otherPublicKey, mySecretKey)
            } catch (e) {
                text = '[Decryption failed]'
            }
            
            return {
                id: msg.id,
                text,
                isOwn,
                type: msg.type,
                timestamp: msg.date_created
            }
        })
    }

    async sendMessage(conversationId, otherPublicKey, plainText, type = 'text', mySecretKey) {
        await cryptoService.ready

        if (!otherPublicKey) throw new Error('Cannot send: recipient key missing')
        
        const { nonce, ciphertext } = cryptoService.encrypt(plainText, otherPublicKey, mySecretKey)

        let senderHash
        if (mySecretKey) {
            const sk = cryptoService.naclUtil.decodeBase64(mySecretKey)
            const kp = cryptoService.nacl.box.keyPair.fromSecretKey(sk)
            const pubB64 = cryptoService.naclUtil.encodeBase64(kp.publicKey)
            senderHash = await this.hashPublicKey(pubB64)
        } else {
            senderHash = await this.getParticipantHash()
        }
        
        const message = await directus.sendMessage(
            conversationId,
            senderHash,
            ciphertext,
            nonce,
            type
        )
        
        this.notifySubscribers()
        
        return {
            id: message.id,
            text: plainText,
            isOwn: true,
            type: message.type,
            timestamp: message.date_created
        }
    }

    async startOrGetConversation(listingId, sellerPublicKey) {
        await cryptoService.ready
        
        const myPublicKey = cryptoService.getPublicKey()
        const myHash = await this.getParticipantHash()
        const sellerHash = await this.hashPublicKey(sellerPublicKey)
        
        const existing = await directus.findConversation(listingId, myHash, sellerHash)
        if (existing) {
            return this.getConversation(existing.id)
        }
        
        const newConv = await directus.startConversation(
            listingId,
            myHash,
            sellerHash,
            myPublicKey,
            sellerPublicKey
        )
        
        return this.getConversation(newConv.id)
    }

    async startOrFindByListing(listingId, sellerPublicKey) {
        return this.startOrGetConversation(listingId, sellerPublicKey)
    }

    async getMySecretKeyForConversation(conversation) {
        if (!conversation?.listing_id) return null
        const listingId = typeof conversation.listing_id === 'object'
            ? conversation.listing_id.id
            : conversation.listing_id
        const listingKey = await cryptoService.getListingSecretKey(listingId)
        return listingKey
    }

    async closeConversation(id) {
        return directus.updateConversationStatus(id, 'closed')
    }

    startPolling(intervalMs = 10000) {
        if (this.pollingInterval) return
        
        this.pollingInterval = setInterval(async () => {
            try {
                await this.getMyConversations()
                this.notifySubscribers()
            } catch (e) {
                console.warn('Conversation polling failed:', e)
            }
        }, intervalMs)
    }

    stopPolling() {
        if (this.pollingInterval) {
            clearInterval(this.pollingInterval)
            this.pollingInterval = null
        }
    }

    subscribe(callback) {
        this.subscribers.add(callback)
        return () => this.subscribers.delete(callback)
    }

    notifySubscribers() {
        this.subscribers.forEach(cb => cb())
    }
}

export const conversationsService = new ConversationsService()
