import config from 'config';
import { format } from 'date-fns';

import {
	buildSchema,
	graphql,
	GraphQLScalarType,
} from 'graphql';

import {
	JSONDefinition,
	JSONResolver,
} from 'graphql-scalars';

import { verifyKey } from '../auth.js';

import {
	actorsSchema,
	fetchActorsByIdGraphql,
	fetchActorsGraphql,
} from './actors.js';

import {
	alertsSchema,
	createAlertGraphql,
	fetchAlertsGraphql,
	fetchNotificationsGraphql,
	removeAlertGraphql,
	updateNotificationGraphql,
	updateNotificationsGraphql,
} from './alerts.js';

import {
	entitiesSchema,
	fetchEntitiesByIdGraphql,
	fetchEntitiesGraphql,
} from './entities.js';

import {
	fetchMoviesByIdGraphql,
	fetchMoviesGraphql,
	moviesSchema,
} from './movies.js';

import {
	fetchScenesByIdGraphql,
	fetchScenesGraphql,
	scenesSchema,
} from './scenes.js';

import {
	createStashGraphql,
	fetchStashGraphql,
	fetchUserStashesGraphql,
	removeStashGraphql,
	stashActorGraphql,
	stashesSchema,
	stashMovieGraphql,
	stashSceneGraphql,
	unstashActorGraphql,
	unstashMovieGraphql,
	unstashSceneGraphql,
	updateStashGraphql,
} from './stashes.js';

const schema = buildSchema(`
	type Query {
		_: Boolean
	}

	type Mutation {
		_: Boolean
	}

	scalar Date

	${JSONDefinition}

	${scenesSchema}
	${moviesSchema}
	${actorsSchema}
	${entitiesSchema}
	${stashesSchema}
	${alertsSchema}
`);

const DateTimeScalar = new GraphQLScalarType({
	name: 'DateTime',
	serialize(value) {
		if (value instanceof Date) {
			return value.toISOString();
		}

		return value;
	},
});

const DateScalar = new GraphQLScalarType({
	name: 'Date',
	serialize(value) {
		if (value instanceof Date) {
			return format(value, 'yyyy-MM-dd');
		}

		return value;
	},
});

export async function graphqlApi(req, res) {
	if (!config.apiAccess.graphqlEnabled) {
		res.status(404).send();
		return;
	}

	await verifyKey(req.headers['api-user'], req.headers['api-key'], req);

	req.user = {
		id: Number(req.headers['api-user']),
	};

	const data = await graphql({
		schema,
		source: req.body.query,
		variableValues: req.body.variables,
		resolvers: {
			DateTimeScalar,
			DateScalar,
			JSON: JSONResolver,
		},
		rootValue: {
			// queries
			scenes: async (query) => fetchScenesGraphql(query, req),
			scene: async (query) => fetchScenesByIdGraphql(query, req),
			scenesById: async (query) => fetchScenesByIdGraphql(query, req),
			movies: async (query) => fetchMoviesGraphql(query, req),
			movie: async (query) => fetchMoviesByIdGraphql(query, req),
			moviesById: async (query) => fetchMoviesByIdGraphql(query, req),
			actors: async (query) => fetchActorsGraphql(query, req),
			actor: async (query, args, info) => fetchActorsByIdGraphql(query, req, info),
			actorsById: async (query, args, info) => fetchActorsByIdGraphql(query, req, info),
			entities: async (query) => fetchEntitiesGraphql(query, req),
			entity: async (query, args, info) => fetchEntitiesByIdGraphql(query, req, info),
			entitiesBySlug: async (query, args, info) => fetchEntitiesByIdGraphql(query, req, info),
			entitiesById: async (query, args, info) => fetchEntitiesByIdGraphql(query, req, info),
			stashes: async (query) => fetchUserStashesGraphql(query, req),
			stash: async (query) => fetchStashGraphql(query, req),
			alerts: async (query) => fetchAlertsGraphql(query, req),
			notifications: async (query) => fetchNotificationsGraphql(query, req),
			// stash mutation
			createStash: async (query) => createStashGraphql(query, req),
			updateStash: async (query) => updateStashGraphql(query, req),
			removeStash: async (query) => removeStashGraphql(query, req),
			stashScene: async (query) => stashSceneGraphql(query, req),
			unstashScene: async (query) => unstashSceneGraphql(query, req),
			stashActor: async (query) => stashActorGraphql(query, req),
			unstashActor: async (query) => unstashActorGraphql(query, req),
			stashMovie: async (query) => stashMovieGraphql(query, req),
			unstashMovie: async (query) => unstashMovieGraphql(query, req),
			// alert mutation
			createAlert: async (query) => createAlertGraphql(query, req),
			removeAlert: async (query) => removeAlertGraphql(query, req),
			updateNotification: async (query) => updateNotificationGraphql(query, req),
			updateNotifications: async (query) => updateNotificationsGraphql(query, req),
		},
	});

	const statusCode = data.errors?.[0]?.originalError?.httpCode || 200;

	res.status(statusCode).send(data);
}
