/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2023 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import type { ExtendedEvent, ShortEvent, SWEvent, EventFight, ResponseGroups, ResponseRelation, ResponseTeam } from "@type/event";
import { fetchWithToken, tokenStore } from "./repo";
import {
    ExtendedEventSchema,
    ShortEventSchema,
    SWEventSchema,
    EventFightSchema,
    ResponseGroupsSchema,
    ResponseRelationSchema,
    ResponseTeamSchema,
    CreateEventGroupSchema,
    UpdateEventGroupSchema,
    CreateEventRelationSchema,
    UpdateEventRelationSchema,
} from "@type/event.js";
import type { CreateEventGroup, UpdateEventGroup, CreateEventRelation, UpdateEventRelation } from "@type/event.js";
import { z } from "zod";
import type { Dayjs } from "dayjs";
import { derived } from "svelte/store";
import { ResponseUserSchema } from "@components/types/data";

export interface CreateEvent {
    name: string;
    start: Dayjs;
    end: Dayjs;
}

export interface UpdateEvent {
    name?: string | null;
    start?: Dayjs | number | null;
    end?: Dayjs | number | null;
    deadline?: Dayjs | number | null;
    maxTeamMembers?: number | null;
    schemType?: string | null;
    publicSchemsOnly?: boolean | null;
    addReferee?: string[] | null;
    removeReferee?: string[] | null;
}

export interface ResponseUser {
    name: string;
    uuid: string;
    prefix: string;
    perms: string[];
}

export class EventRepo {
    constructor(private token: string) {}

    public async listEvents(): Promise<ShortEvent[]> {
        return await fetchWithToken(this.token, "/events")
            .then((value) => value.json())
            .then((value) => z.array(ShortEventSchema).parse(value));
    }

    public async getEvent(id: string): Promise<ExtendedEvent> {
        return await fetchWithToken(this.token, `/events/${id}`)
            .then((value) => value.json())
            .then(ExtendedEventSchema.parse);
    }

    public async createEvent(event: CreateEvent): Promise<SWEvent> {
        return await fetchWithToken(this.token, "/events", {
            method: "POST",
            body: JSON.stringify({
                name: event.name,
                start: +event.start,
                end: +event.end,
            }),
        })
            .then((value) => value.json())
            .then(SWEventSchema.parse);
    }

    public async updateEvent(id: string, event: UpdateEvent): Promise<SWEvent> {
        return await fetchWithToken(this.token, `/events/${id}`, {
            method: "PUT",
            body: JSON.stringify({
                name: event.name,
                start: event.start?.valueOf(),
                end: event.end?.valueOf(),
                deadline: event.deadline?.valueOf(),
                maxTeamMembers: event.maxTeamMembers,
                schemType: event.schemType,
                publicSchemsOnly: event.publicSchemsOnly,
                addReferee: event.addReferee,
                removeReferee: event.removeReferee,
            }),
            headers: {
                "Content-Type": "application/json",
            },
        })
            .then((value) => value.json())
            .then(SWEventSchema.parse);
    }

    public async deleteEvent(id: string): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${id}`, {
            method: "DELETE",
        });

        return res.ok;
    }

    // Fights
    public async listFights(eventId: string): Promise<EventFight[]> {
        return await fetchWithToken(this.token, `/events/${eventId}/fights`)
            .then((value) => value.json())
            .then((value) => z.array(EventFightSchema).parse(value));
    }
    public async createFight(eventId: string, fight: any): Promise<EventFight> {
        delete fight.ergebnis;
        return await fetchWithToken(this.token, `/events/${eventId}/fights`, {
            method: "POST",
            body: JSON.stringify(fight),
            headers: { "Content-Type": "application/json" },
        })
            .then((value) => value.json())
            .then(EventFightSchema.parse);
    }
    public async deleteFight(eventId: string, fightId: string): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/fights/${fightId}`, {
            method: "DELETE",
        });
        return res.ok;
    }

    // Groups
    public async listGroups(eventId: string): Promise<ResponseGroups[]> {
        return await fetchWithToken(this.token, `/events/${eventId}/groups`)
            .then((value) => value.json())
            .then((value) => z.array(ResponseGroupsSchema).parse(value));
    }
    public async createGroup(eventId: number, group: CreateEventGroup): Promise<ResponseGroups> {
        CreateEventGroupSchema.parse(group);
        return await fetchWithToken(this.token, `/events/${eventId}/groups`, {
            method: "POST",
            body: JSON.stringify({
                name: group.name,
                type: group.type,
            }),
            headers: { "Content-Type": "application/json" },
        })
            .then((value) => value.json())
            .then(ResponseGroupsSchema.parse);
    }
    public async getGroup(eventId: string, groupId: string): Promise<ResponseGroups> {
        return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`)
            .then((value) => value.json())
            .then(ResponseGroupsSchema.parse);
    }
    public async updateGroup(eventId: string, groupId: string, group: UpdateEventGroup): Promise<ResponseGroups> {
        UpdateEventGroupSchema.parse(group);
        return await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, {
            method: "PUT",
            body: JSON.stringify(group),
            headers: { "Content-Type": "application/json" },
        })
            .then((value) => value.json())
            .then(ResponseGroupsSchema.parse);
    }
    public async deleteGroup(eventId: string, groupId: string): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/groups/${groupId}`, {
            method: "DELETE",
        });
        return res.ok;
    }

    // Relations
    public async listRelations(eventId: number): Promise<ResponseRelation[]> {
        return await fetchWithToken(this.token, `/events/${eventId}/relations`)
            .then((value) => value.json())
            .then((value) => z.array(ResponseRelationSchema).parse(value));
    }
    public async createRelation(eventId: number, relation: CreateEventRelation): Promise<ResponseRelation> {
        CreateEventRelationSchema.parse(relation);
        return await fetchWithToken(this.token, `/events/${eventId}/relations`, {
            method: "POST",
            body: JSON.stringify(relation),
            headers: { "Content-Type": "application/json" },
        })
            .then((value) => value.json())
            .then(ResponseRelationSchema.parse);
    }
    public async getRelation(eventId: string, relationId: string): Promise<ResponseRelation> {
        return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`)
            .then((value) => value.json())
            .then(ResponseRelationSchema.parse);
    }
    public async updateRelation(eventId: number, relationId: number, relation: UpdateEventRelation): Promise<ResponseRelation> {
        UpdateEventRelationSchema.parse(relation);
        return await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
            method: "PUT",
            body: JSON.stringify(relation),
            headers: { "Content-Type": "application/json" },
        })
            .then((value) => value.json())
            .then(ResponseRelationSchema.parse);
    }
    public async deleteRelation(eventId: number, relationId: number): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/relations/${relationId}`, {
            method: "DELETE",
        });
        return res.ok;
    }

    // Teams
    public async listTeams(eventId: string): Promise<ResponseTeam[]> {
        return await fetchWithToken(this.token, `/events/${eventId}/teams`)
            .then((value) => value.json())
            .then((value) => z.array(ResponseTeamSchema).parse(value));
    }
    public async updateTeams(eventId: string, teams: number[]): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, {
            method: "PUT",
            body: JSON.stringify(teams),
            headers: { "Content-Type": "application/json" },
        });
        return res.ok;
    }
    public async deleteTeams(eventId: string, teams: number[]): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/teams`, {
            method: "DELETE",
            body: JSON.stringify(teams),
            headers: { "Content-Type": "application/json" },
        });
        return res.ok;
    }

    // Referees
    public async listReferees(eventId: string): Promise<ResponseUser[]> {
        return await fetchWithToken(this.token, `/events/${eventId}/referees`)
            .then((value) => value.json())
            .then((value) => z.array(ResponseUserSchema).parse(value));
    }
    public async updateReferees(eventId: string, refereeUuids: string[]): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, {
            method: "PUT",
            body: JSON.stringify(refereeUuids),
            headers: { "Content-Type": "application/json" },
        });
        return res.status === 204;
    }
    public async deleteReferees(eventId: string, refereeUuids: string[]): Promise<boolean> {
        const res = await fetchWithToken(this.token, `/events/${eventId}/referees`, {
            method: "DELETE",
            body: JSON.stringify(refereeUuids),
            headers: { "Content-Type": "application/json" },
        });
        return res.status === 204;
    }
}

export const eventRepo = derived(tokenStore, ($token) => new EventRepo($token));
