/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable prefer-rest-params */
import { DocDb, IDoc as IDocB } from '../db/doc.mjs';
import { DocUserRoleDb, IDocUserRole, Role } from '../db/doc-user-role.mjs';
import { IOpEx, OpsExDb } from '../db/ops-ex.mjs';
import * as OT from "@mescius/js-collaboration-ot";
import { konva_type, rich_text_type, spreadsheet_type } from '../ot-types.mjs';
import { IUserRecord, UserDb } from '../db/users.mjs';
import { DEFAULT_SNAPSHOT } from '../common/default-snapshot.mjs';

import _ from 'lodash';
import jwt from 'jsonwebtoken';
import { importFile } from './import-util.mjs';

const secretKey = 'your-secret-key'; // JWT key

const userDb = new UserDb();
const docDb = new DocDb();
const docUserRoleDb = new DocUserRoleDb();
const opsExDb = new OpsExDb();

export class DocServices {
    documentServices: OT.DocumentServices<any, any>;

    constructor(documentServices: OT.DocumentServices<any, any>) {
        this.documentServices = documentServices;
    }

    async create(userId: string, name: string, type: string): Promise<IDoc> {
        type = type ?? rich_text_type.uri;
        name = name ?? 'Untitled';

        let data: any = '';
        if (type === konva_type.uri) {
            data = [];
        } else if (type === spreadsheet_type.uri) {
            data = _.cloneDeep(DEFAULT_SNAPSHOT);
        } else if (type === rich_text_type.uri) {
            data = '';
        }

        const { id } = await docDb.insert(name, type, userId)
        await docUserRoleDb.insert(userId, id, Role.owner);
        await this.documentServices.submit(id, { v: 0, create: { type, data } }, { options: { userId } });

        return await this.get(userId, id) as IDoc;
    }
    async upload(userId: string, fileBlob): Promise<IDoc> {
        try {
            const data = await importFile(fileBlob);

            const { id } = await docDb.insert(fileBlob.name.split('.')[0], spreadsheet_type.uri, userId)
            await docUserRoleDb.insert(userId, id, Role.owner);
            await this.documentServices.submit(id, { v: 0, create: { type: spreadsheet_type.uri, data: data } }, { options: { userId } });
            return await this.get(userId, id) as IDoc;  
        } catch (error) {
            return { error: (error as Error).message } as any;
        }
    }
    async delete(userId: string, docId: string): Promise<void> {
        const role = await docUserRoleDb.get(userId, docId);
        if (role === Role.owner) {
            await docDb.delete(docId);
            await docUserRoleDb.deleteByDocId(docId);
        } else {
            await docUserRoleDb.delete(userId, docId);
        }
    }

    async rename(docId: string, name: string): Promise<void> {
        await docDb.rename(docId, name);
    }

    async getAllFiles(userId: string): Promise<IDoc[]> {
        const row = await docUserRoleDb.getByUserId(userId);
        const result: IDoc[] = [];

        for (let i = 0; i < row.length; i++) {
            const d = await this._get(row[i].docId, row[i].role)

            result.push(d)
        }

        return result;
    }

    async get(userId: string, docId: string): Promise<IDoc | undefined> {
        const role = await docUserRoleDb.get(userId, docId);

        if (role === Role.none) {
            return;
        }

        return this._get(docId, role);
    }

    async _get(docId: string, role: Role): Promise<IDoc> {
        const d = await docDb.get(docId) as IDocB;
        const u = await userDb.getById(d.createUserId) as IUserRecord;
        const modify_u = await userDb.getById(d.lastModifyUserId) as IUserRecord;

        return { ...d, role, createUser: u, lastModifyUser: modify_u };
    }

    async fetchHistorySnapshot(docId: string, version?: number) {
        return await this.documentServices.fetchHistorySnapshot(docId, version);
    }
    async getOps(docId: string) {
        const opsEx = await opsExDb.getByDocId(docId);

        const result: (IOpEx & { username: string })[] = [];

        for (let i = 0; i < opsEx.length; i++) {
            const d = await userDb.getById(opsEx[i].userId) as IUserRecord;

            result.push({ ...opsEx[i], username: d?.username })
        }

        return result;
    }

    async insertOpEx(docId: string, version: number, userId: string) {
        await opsExDb.insert(userId, docId, version);
    }

    async getUsers(docId: string) {
        const row = await docUserRoleDb.getByDocId(docId);

        const result: (IDocUserRole & { username: string })[] = [];

        for (let i = 0; i < row.length; i++) {
            const d = await userDb.getById(row[i].userId) as IUserRecord;

            result.push({ ...row[i], username: d?.username })
        }

        return result;
    }

    async inviteUsers(docId: string, userIds: string[], role: Role) {
        for (const userId of userIds) {
            await this.setDocUserRole(docId, userId, role);
        }
    }

    async setDocUserRole(docId: string, userId: string, role: Role) {
        await docUserRoleDb.set(userId, docId, role);
    }

    async updateLastModifyInfo(docId: string, userId: string) {
        await docDb.updateLastModifyInfo(docId, userId);
    }

    createShareLink(docId: string, role: Role): string {
        return jwt.sign({ docId, role }, secretKey);
    }

    async verifyShareLink(userId: string, link: string): Promise<{ docId: string, role: string }> {

        const { docId, role } = jwt.verify(link, secretKey) as { docId: string, role: Role };

        await docUserRoleDb.set(userId, docId, role);

        return { docId, role };
    }

    async getRole(userId: string, docId: string) {
        return await docUserRoleDb.get(userId, docId);
    }
}

interface IDoc {
    id: string;
    name: string;
    type: string;
    createUser: IUserRecord;
    lastModifyUser: IUserRecord;
    lastModifyDate: string;
    role: string;
}