const { getDB } = require('../../../utils/mongo')

let db = null

// Database Name
const dbName = 'coder'

// Collections constant
const TOPIC_TAGS_COLLECTION = 'topic-tags'
const TOPIC_CATEGORIES_COLLECTION = 'topic-categories'
const TOPIC_LIST_COLLECTION = 'topic-list'
const TOPIC_COMMENTS_COLLECTION = 'topic-comments'
const USERS_COLLECTION = 'users'

// page size
const PAGE_SIZE = 2

// TODO убрать из export?
const connect = async () => {
    db = await getDB(dbName)
}

const getCategories = async () => {
    // TODO при первом запросе db = null
    if (db === null) {
        throw new Error('no db connection')
    }

    const collection = db.collection(TOPIC_CATEGORIES_COLLECTION)

    const categories = await collection.find({
    }).project({
        _id: 0,
    }).toArray()

    if (categories.length === 0) {
        const newCategories = require('./forum/categories.json').body
        collection.insertMany(newCategories)
        return newCategories
    }
    return categories
}

const getCategoryByPath = async (path) => {
    if (db === null) {
        throw new Error('no db connection')
    }

    const collection = db.collection(TOPIC_CATEGORIES_COLLECTION)

    const category = await collection.findOne({
        path,
    }, {
        projection: {
            _id: 0,
        },
    })

    if (!category) {
        throw new Error('No data')
    }
    return category
}

const getTopicById = async (id) => {
    if (db === null) {
        throw new Error('no db connection')
    }

    const topicsCollection = db.collection(TOPIC_LIST_COLLECTION)

    const topic = await topicsCollection
        .aggregate([
            {
                $match: {
                    id: Number(id),
                },
            },
            {
                $limit: 1,
            },
            {
                $lookup: {
                    from: USERS_COLLECTION, localField: 'authorId', foreignField: 'id', as: 'author',
                },
            },
            {
                $unwind: '$author',
            },
            {
                $lookup: {
                    from: TOPIC_TAGS_COLLECTION, localField: 'tagsId', foreignField: 'id', as: 'tags',
                },
            },
            {
                $project: {
                    id: 0,
                    categoryId: 0,
                    authorId: 0,
                    tagsId: 0,
                    'author.id': 0,
                    'author._id': 0,
                    'tags.id': 0,
                    'tags._id': 0,
                    _id: 0,
                },
            },
        ])
        .toArray()

    if (topic.length === 0) {
        throw new Error('No data')
    }

    return topic[0]
}

const getCommentsByTopicId = async (id, page) => {
    if (db === null) {
        throw new Error('no db connection')
    }

    if (page === undefined) {
        page = 1
    }

    const commentsCollection = db.collection(TOPIC_COMMENTS_COLLECTION)

    let count = await commentsCollection.count({
    }, {
        limit: 1,
    })
    if (count === 0) {
        const newComments = require('./forum/topic-comments.json').body
        commentsCollection.insertMany(newComments)
    }

    const comments = await commentsCollection
        .aggregate([
            {
                $match: {
                    topicId: Number(id),
                },
            },
            {
                $skip: PAGE_SIZE * Number(page) - PAGE_SIZE,
            },
            {
                $limit: PAGE_SIZE,
            },
            {
                $lookup: {
                    from: USERS_COLLECTION, localField: 'authorId', foreignField: 'id', as: 'author',
                },
            },
            {
                $unwind: '$author',
            },
            {
                $project: {
                    topicId: 0,
                    authorId: 0,
                    'author.id': 0,
                    'author._id': 0,
                    _id: 0,
                },
            },
        ])
        .toArray()

    // pagination
    count = await commentsCollection.count({
        topicId: Number(id),
    })
    const result = {
        page,
        pageSize: PAGE_SIZE,
        total: count,
        totalPages: Math.ceil(count / PAGE_SIZE),
        comments,
    }

    return result
}

const getTopicsByCategoryId = async (id, page) => {
    if (db === null) {
        throw new Error('no db connection')
    }

    if (page === undefined) {
        page = 1
    }

    const topicsCollection = db.collection(TOPIC_LIST_COLLECTION)

    let count = await topicsCollection.count({
    }, {
        limit: 1,
    })
    if (count === 0) {
        const newTopics = require('./forum/topic-list.json').body.items
        topicsCollection.insertMany(newTopics)
    }

    // TODO delete after auth implementation
    const usersCollection = db.collection(USERS_COLLECTION)
    const usersCount = await usersCollection.count({
    }, {
        limit: 1,
    })
    if (usersCount === 0) {
        const newUsers = require('./forum/users.json').body
        usersCollection.insertMany(newUsers)
    }

    const topics = await topicsCollection
        .aggregate([
            {
                $match: {
                    categoryId: Number(id),
                },
            },
            {
                $skip: PAGE_SIZE * Number(page) - PAGE_SIZE,
            },
            {
                $limit: PAGE_SIZE,
            },
            {
                $lookup: {
                    from: USERS_COLLECTION, localField: 'authorId', foreignField: 'id', as: 'author',
                },
            },
            {
                $unwind: '$author',
            },
            {
                $project: {
                    id: 1,
                    title: 1,
                    commentCount: 1,
                    viewCount: 1,
                    voteCount: 1,
                    creationDate: 1,
                    'author.name': 1,
                    'author.avatar': 1,
                    _id: 0,
                },
            },
        ])
        .toArray()

    if (topics.length === 0) {
        throw new Error('No data')
    }

    // pagination
    count = await topicsCollection.count({
        categoryId: Number(id),
    })
    const result = {
        page,
        pageSize: PAGE_SIZE,
        total: count,
        totalPages: Math.ceil(count / PAGE_SIZE),
        topics,
    }

    return result
}

const getTags = async () => {
    if (db === null) {
        throw new Error('no db connection')
    }

    const tagsCollection = db.collection(TOPIC_TAGS_COLLECTION)

    const count = await tagsCollection.count({
    }, {
        limit: 1,
    })
    if (count === 0) {
        const newTags = require('./forum/topic-tags.json').body
        tagsCollection.insertMany(newTags)
    }

    const tags = await tagsCollection.find({
    }).project({
        _id: 0,
    }).toArray()

    if (tags.length === 0) {
        throw new Error('No data')
    }

    return tags
}

const findTags = async (value) => {
    if (db === null) {
        throw new Error('no db connection')
    }
    const tagsCollection = db.collection(TOPIC_TAGS_COLLECTION)

    const tags = await tagsCollection
        .find({
            name: {
                $regex: `${value}`,
            },
        })
        .project({
            _id: 0,
        })
        .toArray()

    return tags
}

const insertTag = async (value) => {
    if (db === null) {
        throw new Error('no db connection')
    }
    const tagsCollection = db.collection(TOPIC_TAGS_COLLECTION)
    // TODO no fast, reimplement
    const count = await tagsCollection.estimatedDocumentCount()

    await tagsCollection.insertOne({
        id: count + 1,
        name: value,
        numTopics: 0,
    })

    return count + 1
}

const insertComment = async (comment) => {
    if (db === null) {
        throw new Error('no db connection')
    }
    const commentsCollection = db.collection(TOPIC_COMMENTS_COLLECTION)
    // TODO no fast, reimplement
    // TODO может перейти на _id?
    const count = await commentsCollection.estimatedDocumentCount()

    const currentTime = Math.round(new Date().getTime() / 1000)

    await commentsCollection.insertOne({
        id: count + 1,
        topicId: comment.topicId,
        voteCount: 0,
        content: comment.content,
        updationDate: currentTime,
        creationDate: currentTime,
        authorId: comment.authorId,
        authorIsModerator: false,
        isOwnPost: false,
    })

    return comment
}

const insertTopic = async (topic) => {
    if (db === null) {
        throw new Error('no db connection')
    }
    const topicsCollection = db.collection(TOPIC_LIST_COLLECTION)
    // TODO no fast, reimplement
    // TODO может перейти на _id?
    const count = await topicsCollection.estimatedDocumentCount()

    const currentTime = Math.round(new Date().getTime() / 1000)

    const tagsId = []

    if (topic.tags) {
        for (let index = 0; index < topic.tags.length; index++) {
            const tag = topic.tags[index]
            if (tag.id === 0) {
                const tagId = await insertTag(tag.name)
                tagsId.push(tagId)
            } else {
                tagsId.push(tag.id)
            }
        }
    }

    const result = await topicsCollection.insertOne({
        id: count + 1,
        categoryId: topic.categoryId,
        title: topic.title,
        commentCount: 0,
        viewCount: 0,
        tagsId,
        voteCount: 0,
        voteStatus: 0,
        content: topic.content,
        updationDate: currentTime,
        creationDate: currentTime,
        authorId: topic.authorId,
        isOwnPost: true,
    })

    if (!result.insertedId) {
        throw new Error('Insert data failed, try again later')
    }

    return count + 1
}

module.exports = {
    connect,
    getTags,
    findTags,
    insertTag,
    getCategories,
    getTopicsByCategoryId,
    getTopicById,
    getCommentsByTopicId,
    insertComment,
    insertTopic,
    getCategoryByPath,
}
