import * as dotenv from 'dotenv';

dotenv.config();

const mariadb = require('mariadb');
const prod_pool = mariadb.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.PARTYPLANER_PROD_DATABASE,
    connectionLimit: 5
});
const dev_pool = mariadb.createPool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    database: process.env.PARTYPLANER_DEV_DATABASE,
    connectionLimit: 5
});

/**
 * Used in the getUserData method as return value
 */
export interface UserData {
    username: string;
    email: string;
    firstName: string;
    lastName: string;
    lastLogin: string;
    emailIsVerified: string;
    isPremiumUser: string;
}

/**
 * Used in the getSessionData method as return value
 */
export interface SessionData {
    sessionId: string;
    type: string;
    lastLogin: string;
    lastIp: string;
}

/**
 * Used in the getFriendshipData method as a return value
 */
export interface Friendship {
    friendshipId: string;
    friendId: string;
    friendFirstName: string;
    friendLastName: string;
    friendUsername: string;
}

/**
 * Used in the getEventData method
 */
export interface Invite {
    inviteId: string;
    inviteKey: string;
    validUntil: Date;
    alreadyUsed: boolean;
    invitedPersonName: string;
}

/**
 * Used in the getEventData method
 */
export interface Registration {
    registrationId: string;
    name: string;
    registeredDate: Date;
    takesPart: boolean;
    comment: string;
}

/**
 * Used in the getEventData method as a return value
 */
export interface Event {
    eventId: string;
    name: string;
    description: string;
    takesPlaceDate: Date;
    registrationUntilDate: Date;
    maxParticipants: number;
    invites: Invite[];
    registrations: Registration[];
}

/**
 * Returns all data about the given user
 * @param useDev If the dev or prod database should be used
 * @param userId The userId of the user to return the data for
 * @return UserData An object containing the user data
 */
export const getUserData = async (useDev: boolean, userId: string): Promise<UserData> => {
    let conn;
    try {
        if (useDev) {
            conn = await dev_pool.getConnection();
        } else {
            conn = await prod_pool.getConnection();
        }

        let rows = await conn.query('SELECT username, email, first_name, last_Name, last_login, email_is_verified, is_premium_user FROM users WHERE user_id = ?', userId);

        let user: UserData = {} as UserData;

        for (let row of rows) {
            user = {
                username: row.username,
                email: row.email,
                firstName: row.first_name,
                lastName: row.last_name,
                lastLogin: row.last_login,
                emailIsVerified: row.email_is_verified,
                isPremiumUser: row.is_premium_user
            };
        }

        return user;
    } catch (err) {
        throw err;
    } finally {
        if (conn) {
            conn.end();
        }
    }
};

/**
 * Returns all active sessions of the given user
 * @param useDev If the dev or prod database should be used
 * @param userId The userId of the user to return the sessions for
 * @return SessionData[] A list containing objects with the session data
 */
export const getSessionData = async (useDev: boolean, userId: string): Promise<SessionData[]> => {
    let conn;
    try {
        if (useDev) {
            conn = await dev_pool.getConnection();
        } else {
            conn = await prod_pool.getConnection();
        }

        let rows = await conn.query('SELECT session_id, type, last_login, last_ip FROM sessions WHERE user_id = ? AND valid_until > NOW()', userId);

        let sessions: SessionData[] = [];

        for (let row of rows) {
            sessions.push({
                sessionId: row.session_id,
                type: row.type,
                lastLogin: row.last_login,
                lastIp: row.last_ip
            });
        }

        return sessions;
    } catch (err) {
        throw err;
    } finally {
        if (conn) {
            conn.end();
        }
    }
};

/**
 * Returns all friends of the given user
 * @param useDev If the dev or prod database should be used
 * @param userId The userId of the user to fetch the friends for
 * @return Friendship[] A list of friends
 */
export const getFriendshipData = async (useDev: boolean, userId: string): Promise<Friendship[]> => {
    let conn;
    try {
        if (useDev) {
            conn = await dev_pool.getConnection();
        } else {
            conn = await prod_pool.getConnection();
        }

        let rows = await conn.query('SELECT f.friendship_id, f.friend_id, u.first_name as friend_first_name, u.last_name as friend_last_name, u.username as friend_username FROM friendships f LEFT OUTER JOIN users u ON f.friend_id = u.user_id WHERE f.user_id = ?', userId);

        let friends: Friendship[] = [];

        for (let row of rows) {
            friends.push({
                friendshipId: row.friendship_id,
                friendId: row.friend_id,
                friendFirstName: row.friend_first_name,
                friendLastName: row.friend_last_name,
                friendUsername: row.friend_username
            });
        }

        return friends;
    } catch (err) {
        throw err;
    } finally {
        if (conn) {
            conn.end();
        }
    }
};

/**
 * Returns all events of the given user
 * @param useDev If the dev or prod database should be used
 * @param userId The userId of the user to fetch the friends for
 * @return Event[] A list of events
 */
export const getEventData = async (useDev: boolean, userId: string): Promise<Event[]> => {
    let conn;
    try {
        if (useDev) {
            conn = await dev_pool.getConnection();
        } else {
            conn = await prod_pool.getConnection();
        }

        let eventRows = await conn.query('SELECT event_id, name, description, takes_place_date, registration_until_date, max_participants FROM events WHERE creator_id = ?', userId);

        let eventsMap = new Map<string, Event>();
        let eventIds: string[] = [];

        for (let row of eventRows) {
            eventIds.push(row.event_id);
            let event = {
                eventId: row.event_id,
                name: row.name,
                description: row.description,
                takesPlaceDate: row.takes_place_date,
                registrationUntilDate: row.registration_until_date,
                maxParticipants: row.max_participants,
                invites: [],
                registrations: []
            };
            eventsMap.set(row.event_id, event);
        }

        let registrationRows = await conn.query('SELECT registration_id, name, registered_date, takes_part, comment, event_id FROM event_registration WHERE event_id IN (?)', eventIds);

        for (let row of registrationRows) {
            let event = eventsMap.get(row.event_id);
            if (!event) continue;
            event.registrations.push({
                registrationId: row.registration_id,
                name: row.name,
                registeredDate: row.registered_date,
                takesPart: row.takes_part,
                comment: row.comment
            });
        }

        let inviteRows = await conn.query('SELECT invite_id, invite_key, valid_until, already_used, invited_person_name, event_id FROM invitations WHERE event_id IN (?)', eventIds);

        for (let row of inviteRows) {
            let event = eventsMap.get(row.event_id);
            if (!event) continue;
            event.invites.push({
                inviteId: row.invite_id,
                inviteKey: row.invite_key,
                validUntil: row.valid_until,
                alreadyUsed: row.already_used,
                invitedPersonName: row.invited_person_name
            });
        }
        
        let eventsList: Event[] = [];
        for (let event of eventsMap.values()) {
            eventsList.push(event);
        }

        return eventsList;
    } catch (err) {
        throw err;
    } finally {
        if (conn) {
            conn.end();
        }
    }
};