import { string, StringSchema, SchemaDescription, TestMessageParams } from 'yup';
import { MeetingBackend, MeetingStatus, QueueHost } from "./models";
import { getUser } from "./services/api";

// Yup: https://github.com/jquense/yup


// Utilities

function getMaxLimit (description: SchemaDescription): number | undefined {
    const matches = description.tests.filter((obj: any) => obj.params?.max);
    if (matches.length !== 1) {
        console.error('Invalid use of getMaxLimit: ' + matches);
    }
    return matches.length === 1 ? matches[0].params.max : undefined;
}

function createRemainingCharsMessage (data: { max: number; } & Partial<TestMessageParams>): string {
    const remaining = data.max - data.value.length;
    const charsRemaining = (remaining > 0) ? remaining : 0;
    const charsOver = (remaining < 0) ? ` (${remaining * -1} over limit)` : '';
    return `Remaining characters: ${charsRemaining}/${data.max}${charsOver}`;
}

const createInvalidUniqnameMessage = (uniqname: string) => (
    uniqname + " is not a valid user. " +
    "Please make sure the uniqname is correct, and that they have logged onto Remote Office Hours Queue at least once."
);

export const confirmUserExists = async (uniqname: string) => {
    const sanitizedUniqname = uniqname.trim().toLowerCase();
    try {
        return await getUser(sanitizedUniqname);
    } catch (err) {
        throw err.name === "NotFoundError"
            ? new Error(createInvalidUniqnameMessage(sanitizedUniqname))
            : err;
    }
}

export const validatePhoneNumber = (phone: string, countryDialCode: string): Error[] => {
    return [
        (countryDialCode === '1' && phone.length !== 11)
            && new Error(
                'Please enter a valid US/Canada phone number with area code ' +
                '(11 digits including +1 country code) that can receive SMS messages.'
            ),
    ].filter(e => e) as Error[];
}


// Schemas

const blankText = 'This field may not be left blank.';

export const queueNameSchema = string().trim().required(blankText).max(100, createRemainingCharsMessage);
export const queueDescriptSchema = string().trim().max(1000, createRemainingCharsMessage);
export const meetingAgendaSchema = string().trim().max(100, createRemainingCharsMessage);
export const queueLocationSchema = string().trim().max(100, createRemainingCharsMessage);
export const uniqnameSchema = string().trim().lowercase()
    .min(3, 'Uniqnames must be at least 3 characters long.')
    .max(8, 'Uniqnames must be at most 8 characters long.')
    .matches(/^[a-z]+$/i, 'Uniqnames cannot contain non-alphabetical characters.');


// Type validator(s)

export interface ValidationResult {
    transformedValue: string;
    isInvalid: boolean;
    messages: ReadonlyArray<string>;
}

export function validateString (value: string, schema: StringSchema, showRemaining: boolean): ValidationResult {
    let transformedValue;
    let isInvalid = false;
    let messages = Array();
    try {
        transformedValue = schema.validateSync(value);
        const maxLimit = getMaxLimit(schema.describe());
        if (showRemaining && maxLimit) {
            messages.push(createRemainingCharsMessage({'value': transformedValue, 'max': maxLimit}));
        }
    } catch (error) {
        transformedValue = error.value;
        isInvalid = true;
        messages = error.errors;
    }
    return {'transformedValue': transformedValue, 'isInvalid': isInvalid, 'messages': messages};
}


export interface MeetingTypesValidationResult {
    isInvalid: boolean;
    messages: ReadonlyArray<string>;
}

export function validateMeetingTypes (value: Set<string>, backends: MeetingBackend[], queue?: QueueHost): MeetingTypesValidationResult {
    let messages = [];

    const noTypesSelected = value.size === 0;
    if (noTypesSelected) messages.push('You must select at least one allowed meeting type.');

    let existingMeetingConflict = false;
    if (queue) {
        const unstartedMeetings = queue!.meeting_set.filter(m => m.status !== MeetingStatus.STARTED);
        const uniqueUnstartedMeetingTypes = new Set(unstartedMeetings.map(m => m.backend_type));
        const conflictingTypes = [...uniqueUnstartedMeetingTypes]
            .filter(uniqueMeetingType => !value.has(uniqueMeetingType));
        const conflictingTypeNames = conflictingTypes
            .map(ct => backends.find(b => b.name === ct)?.friendly_name ?? ct);
        if (conflictingTypes.length > 0) {
            existingMeetingConflict = true;
            messages.push(
                'You cannot disallow the following meeting types until the meetings ' +
                'using them have been removed from the queue: ' +
                conflictingTypeNames.join(', ')
            );
        }
    }
    return { isInvalid: (noTypesSelected || existingMeetingConflict), messages: messages };
}