import https from 'https';
import { URLSearchParams } from 'url';
import _ from 'lodash';
import { SanityClient as SanityClientType, SanityDocument } from '@sanity/client';
import { isDraftId } from './sanity-document-converter';
export { default as SanityClient } from '@sanity/client';

export interface SanityUser {
    id: string;
    projectId: string;
    displayName: string;
    email?: string;
    familyName: string | null;
    givenName: string | null;
    middleName: string | null;
    imageUrl: string | null;
    createdAt: string;
    updatedAt: string;
    sanityUserId: string;
    provider: string;
}

export interface DocumentHistory {
    id: string;
    timestamp: string;
    author: string;
    documentIDs: string[];
}

export type DocumentHistoryMap = Record<string, DocumentHistory[]>;

export async function testToken(apiToken: string): Promise<{
    accessToken?: string;
    accessTokenExpiresAt?: string;
    client?: { id?: string };
    user?: { id?: string };
}> {
    return new Promise((resolve, reject) => {
        let output = '';

        const req = https.request(
            {
                hostname: 'api.sanity.io',
                path: `/v1/auth/oauth/tokens/${apiToken}`,
                method: 'GET'
            },
            (res) => {
                res.setEncoding('utf8');
                res.on('data', (chunk) => {
                    output += chunk;
                });
                res.on('end', () => {
                    let response = null;
                    try {
                        response = JSON.parse(output);
                    } catch (error) {
                        reject(error);
                        return;
                    }
                    if (res.statusCode !== 200) {
                        reject(response);
                        return;
                    }
                    resolve(response);
                });
            }
        );

        req.on('error', (error) => {
            reject(error);
        });

        req.end();
    });
}

export async function fetchDocumentsHistory({
    documentIds,
    dataset,
    client,
    limitTime = true
}: {
    documentIds: string[];
    dataset: string;
    client: SanityClientType;
    limitTime?: boolean;
}): Promise<DocumentHistory[]> {
    if (!documentIds.length) {
        return [];
    }
    // max size of one request to Sanity is 32Kb
    // Sanity response: AssertionError [ERR_ASSERTION]: Your message must be < 32kb.
    // it's undocumented, so 11Kb is query limit - https://www.sanity.io/docs/http-query, hence stick to that
    const chunkedDocumentIds = chunkArray(documentIds, 11000);
    const result: DocumentHistory[] = [];
    for (const idsChunk of chunkedDocumentIds) {
        const searchParams = new URLSearchParams();
        searchParams.append('excludeContent', 'true');
        searchParams.append('reverse', 'true');
        if (limitTime) {
            // get docs history from last 30 days
            searchParams.append('fromTime', new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString());
        }
        const data = await client.request({
            uri: `data/history/${dataset}/transactions/${idsChunk.join(',')}?${searchParams.toString()}`
        });
        for (const line of data.split('\n')) {
            if (line) {
                result.push(JSON.parse(line));
            }
        }
    }
    return result;
}

export async function fetchDocumentRevision({
    documentId,
    draftDocumentId,
    versionId,
    dataset,
    client
}: {
    documentId: string;
    draftDocumentId?: string;
    versionId: string;
    dataset: string;
    client: SanityClientType;
}): Promise<DocumentHistory> {
    const searchParams = new URLSearchParams();
    searchParams.append('excludeContent', 'true');
    searchParams.append('fromTransaction', versionId);
    searchParams.append('toTransaction', versionId);
    const documentIds = [documentId, draftDocumentId];
    const data = await client.request({
        uri: `data/history/${dataset}/transactions/${documentIds.join(',')}?${searchParams.toString()}`
    });
    return JSON.parse(data);
}

export async function fetchDocumentForRevision({
    documentId,
    draftDocumentId,
    versionId,
    dataset,
    client
}: {
    documentId: string;
    draftDocumentId?: string;
    versionId: string;
    dataset: string;
    client: SanityClientType;
}): Promise<SanityDocument> {
    const documentIds = [documentId, draftDocumentId];
    const data = await client.request({
        uri: `data/history/${dataset}/documents/${documentIds.join(',')}?revision=${versionId}`
    });
    // take either latest draft revision object, or latest object
    return data.documents.find((object: SanityDocument) => isDraftId(object._id)) ?? data.documents[data.documents.length - 1];
}

export async function fetchUsers(userIds: string[], client: SanityClientType): Promise<SanityUser[]> {
    const chunkedDocumentIds = chunkArray(userIds, 11000);
    const result = [];
    for (const idsChunk of chunkedDocumentIds) {
        const data = await client.request({
            uri: `users/${idsChunk.join(',')}`
        });

        // sanity respond differently for one user and multiple users
        // users/p-3vin4nyLUym1 => { 'id': 'p-3vin4nyLUym1', displayName: ... }
        // users/p-3vin4nyLUym1,p12Yq4phv => [{ 'id': 'p-3vin4nyLUym1', displayName: ... }, { 'id': 'p12Yq4phv', displayName: ... }]
        result.push(...(_.isArray(data) ? data : [data]));
    }

    return result;
}

export async function fetchScheduledActions(
    opts: { projectId: string; dataset: string },
    client: SanityClientType,
    filters?: { documentIds?: string[]; state?: string }
) {
    const uri = `/schedules/${opts.projectId}/${opts.dataset}`;
    const result = await client.request({ method: 'GET', uri: uri, query: filters });
    return result?.schedules ?? [];
}

export function chunkArray(arr: string[], maxChunkLength: number): string[][] {
    const chunks = [];
    let currentChunkLength = 0;
    let currentChunk = [] as string[];

    for (const [index, val] of arr.entries()) {
        if (val.length + currentChunkLength >= maxChunkLength) {
            if (currentChunk.length) {
                chunks.push(currentChunk);
            }
            currentChunkLength = val.length;
            currentChunk = [val];
        } else {
            currentChunk.push(val);
            currentChunkLength += val.length + 1; // for comma
        }

        if (arr.length === index + 1) {
            chunks.push(currentChunk);
        }
    }
    return chunks.length ? chunks : [arr];
}
