import { childLogger, type Logger } from "@foxxmd/logging";
import type EventEmitter from "events";
import normalizeUrl from "normalize-url";
import type {PlayObject} from "../../core/Atomic.ts";
import { buildTrackString, capitalize } from "../../core/StringUtils.ts";
import { isNodeNetworkException } from "../common/errors/NodeErrors.ts";
import type {FormatPlayObjectOptions, TimeRangeListensFetcher} from "../common/infrastructure/Atomic.ts";
import type {MalojaClientConfig} from "../common/infrastructure/config/client/maloja.ts";
import type {MalojaScrobbleRequestData} from "../common/vendor/maloja/interfaces.ts";
import AbstractScrobbleClient from "./AbstractScrobbleClient.ts";
import { MalojaApiClient, formatPlayObj as formatMalojaScrobbleToPlay, playToScrobblePayload } from "../common/vendor/maloja/MalojaApiClient.ts";
import { createGetScrobblesForTimeRangeFunc } from "../utils/ListenFetchUtils.ts";

const feat = ["ft.", "ft", "feat.", "feat", "featuring", "Ft.", "Ft", "Feat.", "Feat", "Featuring"];

export default class MalojaScrobbler extends AbstractScrobbleClient {

    requiresAuth = true;
    serverVersion: any;
    webUrl: string;

    api: MalojaApiClient;
    getScrobblesForTimeRange: TimeRangeListensFetcher

    declare config: MalojaClientConfig

    constructor(name: any, config: MalojaClientConfig, emitter: EventEmitter, logger: Logger) {
        super('maloja', name, config, emitter, logger);
        this.api = new MalojaApiClient(name, this.config.data, { logger: childLogger(this.logger, 'API') });
        this.MAX_INITIAL_SCROBBLES_FETCH = 100;
        this.getScrobblesForTimeRange = createGetScrobblesForTimeRangeFunc(this.api, this.api.logger);
    }

    formatPlayObj = (obj: any, options: FormatPlayObjectOptions = {}) => formatMalojaScrobbleToPlay(obj, { url: this.webUrl });

    protected async doBuildInitData(): Promise<true | string | undefined> {
        const { data: { url, apiKey } = {} } = this.config;
        if (apiKey === undefined) {
            throw new Error("'apiKey' not found in config!");
        }
        if (url === undefined) {
            throw new Error("Missing 'url' for Maloja config");
        }
        this.webUrl = normalizeUrl(url);
        return true;
    }

    protected async doCheckConnection(): Promise<true | string | undefined> {

        try {
            await this.api.testConnection();
            await this.api.testHealth();
            return true;
        } catch (e) {
            throw e;
        }

    }


    doAuthentication = async () => {

        const { data: { url, apiKey } = {} } = this.config;
        if (apiKey === undefined) {
            throw new Error("'apiKey' not found in config!");
        }
        try {
            await this.api.testAuth();
            return true;
        } catch (e) {
            if (isNodeNetworkException(e)) {
                this.logger.error('Could not communicate with Maloja API');
            }
            throw e;
        }
    }
    
    public playToClientPayload(playObj: PlayObject): MalojaScrobbleRequestData {

        const { apiKey } = this.config.data;

        return playToScrobblePayload(playObj);
    }

    doScrobble = async (playObj: PlayObject) => {
        const {
            meta: {
                source,
                newFromSource = false,
            } = {}
        } = playObj;

        const scrobbleData = playToScrobblePayload(playObj);

        try {
            const result = await this.api.scrobble(playObj);
            const scrobbleInfo = `Scrobbled (${newFromSource ? 'New' : 'Backlog'})     => (${source}) ${buildTrackString(playObj)}`;
            if (result.warnings?.length > 0) {
                this.logger.warn(`${scrobbleInfo} | ${result.warnings.join(' | ')}`);
            } else {
                this.logger.info(scrobbleInfo);
            }
            return result;
        } catch (e) {
            await this.notify({ title: `Client - ${capitalize(this.type)} - ${this.name} - Scrobble Error`, message: `Failed to scrobble => ${buildTrackString(playObj)} | Error: ${e.message}`, priority: 'error' });
            throw e;
        }
    }
}