import { Express } from 'express';
import { Logger } from "@foxxmd/logging";
import passport from "passport";
import { ExpressHandler } from "../common/infrastructure/Atomic.js";
import LastfmScrobbler from "../scrobblers/LastfmScrobbler.js";
import ScrobbleClients from "../scrobblers/ScrobbleClients.js";
import LastfmSource from "../sources/LastfmSource.js";
import ScrobbleSources from "../sources/ScrobbleSources.js";
import SpotifySource from "../sources/SpotifySource.js";
import YTMusicSource from "../sources/YTMusicSource.js";
import { sortAndDeduplicateDiagnostics } from "typescript";
import { source } from "common-tags";
import TealScrobbler from "../scrobblers/TealfmScrobbler.js";
import { parseRegexSingle } from "@foxxmd/regex-buddy-core";
import { BlueSkyOauthApiClient } from "../common/vendor/bluesky/BlueSkyOauthApiClient.js";
import LibrefmScrobbler from "../scrobblers/LibrefmScrobbler.js";
import LibrefmSource from "../sources/LibrefmSource.js";
import e from "express";
import AbstractSource from "../sources/AbstractSource.js";

export const setupAuthRoutes = (app: Express, logger: Logger, sourceMiddle: ExpressHandler, clientMiddle: ExpressHandler, scrobbleSources: ScrobbleSources, scrobbleClients: ScrobbleClients) => {
    app.use('/api/client/auth', clientMiddle);
    app.get('/api/client/auth', async (req, res) => {
        const {
            scrobbleClient,
        } = req as any;

        switch (scrobbleClient.type) {
            case 'lastfm':
            case 'librefm':
                res.redirect(scrobbleClient.api.getAuthUrl());
                break;
            case 'tealfm':
                const url = await scrobbleClient.getAuthorizeUrl();
                res.redirect(url);
                break;
            default:
                return res.status(400).send(`Specified client does not have auth implemented (${scrobbleClient.type})`);
        }
    });

    app.use('/api/source/auth', sourceMiddle);
    app.get('/api/source/auth', async (req, res, next) => {
        const {
            // @ts-expect-error TS(2339): Property 'scrobbleSource' does not exist on type '... Remove this comment to see the full error message
            scrobbleSource: source,
            // @ts-expect-error TS(2339): Property 'sourceName' does not exist on type 'Requ... Remove this comment to see the full error message
            sourceName: name,
        } = req;

        switch (source.type) {
            case 'spotify':
                if (source.spotifyApi === undefined) {
                    res.status(400).send('Spotify configuration is not valid');
                } else {
                    logger.info('Redirecting to spotify authorization url');
                    res.redirect(source.createAuthUrl());
                }
                break;
            case 'lastfm':
            case 'librefm':
                res.redirect(source.api.getAuthUrl());
                break;
            case 'deezer':
                // @ts-expect-error TS(2339): Property 'deezerSource' does not exist on type 'Se... Remove this comment to see the full error message
                req.session.deezerSource = name;
                return passport.authenticate(`deezer-${source.name}`)(req,res,next);
            case 'ytmusic':
                await (source as YTMusicSource).reauthenticate();
                res.redirect((source as YTMusicSource).verificationUrl);
                break;
            default:
                return res.status(400).send(`Specified source does not have auth implemented (${source.type})`);
        }
    });

    app.get(/.*callback$/, async (req, res, next) => {
        if(req.url.indexOf('/api') !== 0) {
            return res.redirect(307, `/api${req.url}`);
        }
        const {
            query: {
                state,
                name
            } = {}
        } = req;
        const entityTypeHint = req.url.includes('lastfm') ? 'lastfm' : 'librefm';

        if (req.url.includes('lastfm') || req.url.includes('librefm')) {
            const {
                query: {
                    token
                } = {}
            } = req;
            let entity: LastfmScrobbler | LastfmSource | LibrefmScrobbler | LibrefmSource | undefined;
            if(req.url.includes('lastfm')) {
                entity = scrobbleClients.getByNameAndType(state as string, 'lastfm', true) as (LastfmScrobbler | LastfmSource | undefined);
            } else {
                entity = scrobbleClients.getByNameAndType(state as string, 'librefm', true) as (LibrefmScrobbler | LibrefmSource | undefined);
            }

            if(entity === undefined) {
                entity = scrobbleSources.getByName(state, true) as LastfmSource | LibrefmSource;
            }
            try {
                if(entity === undefined) {
                    const error = new Error(`No source/client with type '${entityTypeHint}' and name ${state} was found`);
                    logger.error(error);
                    throw error;
                }
                await entity.api.authenticate(token);
                entity.authFailure = false;
                if(entity instanceof AbstractSource) {
                    entity.poll().catch((e) => logger.error(e));
                } else {
                    entity.initialize()
                    .catch((e) => logger.error(e))
                    .then(() => {
                        entity.initScrobbleMonitoring().catch((e) => logger.error(e));
                    })
                }
                return res.send('OK');
            } catch (e) {
                return res.send(e.message);
            }
        } else if(req.url.includes('ytmusic')) {
            const entity: YTMusicSource | undefined = scrobbleSources.getByName(name) as (YTMusicSource | undefined);
            if(entity === undefined) {
                logger.error(`No YTMUsic source with name ${state} was found`);
            }
            const result = await entity.handleAuthCodeCallback(req.query);
            let responseContent = 'OK';
            if(result === true) {
                entity.authFailure = false;
                entity.poll().catch((e) => logger.error(e));
            } else {
                responseContent = result;
            }
            return res.send(responseContent);
        } else {
            // TODO right now all sources requiring source interaction are covered by logic branches (deezer above and spotify here)
            // but eventually should update all source callbacks to url specific URLS to avoid ambiguity...
            // wish we could use state param to identify name/source but not all auth strategies and auth provides may provide access to that
            logger.info({label: 'Spotify'}, 'Received auth code callback from Spotify');
            const source = scrobbleSources.getByNameAndType(state as string, 'spotify', true) as SpotifySource;
            const tokenResult = await source.handleAuthCodeCallback(req.query);
            let responseContent = 'OK';
            if (tokenResult === true) {
                source.authFailure = false;
                source.poll().catch((e) => logger.error(e));
            } else {
                responseContent = tokenResult;
            }
            return res.send(responseContent);
        }
    });

    app.get(/(\/api\/tealfm\/.*)/, async function (req, res) {

        const clients = scrobbleClients.getByType('tealfm') as TealScrobbler[];
        if (clients.length === 0) {
            logger.warn('Received callback to Teal OAuth but no TealFM scrobble clients are configured');
        }

        // const {
        //     query: {
        //         state
        //     } = {}
        // } = req;

        // const name = (state as string).replace('tfm','');

        // const validClient = clients.find(x => x.name === name);

        // if (validClient === undefined) {
        //     logger.warn(`No Tealfm scrobble matched => URL: ${req.originalUrl} | State: ${state}`);
        // }

        const intents = getTealUrlIntent(req.originalUrl);

        if(intents === undefined) {
            logger.warn(`Tealfm url was not formed correctly. Should be '/api/tealfm/SCROBBLER_NAME/SOME_ACTION' but found ${req.originalUrl}`);
            return res.status(404);
        }

        const validClient = clients.find(x => x.name === intents[0]);
        if(validClient === undefined) {
            logger.warn(`No Tealfm client found with the name ${intents[0]}. Url: ${req.originalUrl}`);
            return res.status(404);
        }

        if(intents[1].includes('login')) {
                    const {
            query: {
                handle
            } = {}
        } = req;
            const url = await (validClient.client as BlueSkyOauthApiClient).createAuthorizeUrl(handle as string);
            res.redirect(url)
        }

        if(intents[1].includes('client-metadata.json')) {
            return res.json((validClient.client as BlueSkyOauthApiClient).getMetadata());
        }

        if(intents[1].includes('oauth/callback')) {
            const result = await (validClient.client as BlueSkyOauthApiClient).handleCallback(new URLSearchParams(req.query as Record<string, string>));
            if(result) {
                return res.status(200);
            }
            return res.status(500);
        }
    });
}

const TEAL_NAME_REGEX = new RegExp(/\/api\/tealfm\/([^\/])\/(.*)/);
const getTealUrlIntent = (url: string): [string, string] | undefined => {
    const res = parseRegexSingle(TEAL_NAME_REGEX, url);
    if(res === undefined) {
        return undefined;
    }
    return res.groups as [string, string];
}
