<?php

namespace App\Repositories;

use App\Box;
use App\CalendarTemplate;
use App\Pricing;
use App\TrainingSession;
use App\CalendarWeek;
use App\News;
use App\TrainingType;
use App\TrainingWorkout;
use App\User;
use App\UserBenchmark;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;

class BoxRepository implements RepositoryInterface{
    public static function all() {
        return Box::all();
    }

    public static function index() {
        return BoxRepository::all();
    }

    public static function create(array $data){
        //1. Mirar que la companyia pugui tenir un altre box
        $company = CompanyRepository::find($data["company_id"]);
        $n_boxes_plan = $company->active_plan ? $company->active_plan->plan->n_boxes : null;
        $n_boxes_now = sizeof($company->boxes);
        if( $n_boxes_plan!=null && $n_boxes_plan!=-1 && ($n_boxes_now >= $n_boxes_plan) ) throw new \Exception("No puedes crear otro box, la tarifa no te lo permite. Cambia de tarifa.");

        //2. Crear
        $box = Box::create($data);
        //Ara ja ho fa automaticament a nivell de model - $box->box_settings()->attach(1,["value" => "default"]);

        return $box;
    }

    public static function update(array $data, $id){
        Box::findOrFail($id)->update($data);
        return Box::findOrFail($id);
    }

    public static function delete($id){
        return Box::destroy($id);
    }

    public static function find($id){
        return Box::findOrFail($id);
    }

    public static function users($id, bool $active = false) {
        $res = [];

        foreach(BoxRepository::userSubscriptions($id, ["subscription_status" => $active ? 2 : 1]) as $s) {
            array_push($res, $s->user);
        }

        return $res;
    }

    public static function settings($id){
        return Box::findOrFail($id)->box_settings;
    }

    public static function updateSettings($id, array $settings){
        $box_settings_rel = Box::findOrFail($id)->box_settings();
        foreach($settings as $set) {
            $box_settings_rel->updateExistingPivot($set['id'], ["value" => $set["pivot"]["value"] ]);
        }

        return Box::find($id);
    }

    public static function calendarTemplate($id){
        return Box::findOrFail($id)->calendarTemplates;
    }

    public static function trainingRooms($id) {
        return Box::findOrFail($id)->trainingRooms;
    }

    public static function calendarTemplates($id) {
        return Box::findOrFail($id)->calendarTemplates;
    }

    public static function trainingSessions($id, $training_room_id = null, $date_start = null, $date_end = null) {
        $whereClause = [ ["box_id", $id] ];
        if($date_start != null) array_push($whereClause, ["day", ">=", Carbon::createFromFormat("Y-m-d H:i:s", $date_start)->format("Y-m-d")]);
        if($date_end != null) array_push($whereClause, ["day", "<=", Carbon::createFromFormat("Y-m-d H:i:s", $date_end)->format("Y-m-d")]);
        if($training_room_id != null) array_push($whereClause, ["training_room_id", $training_room_id]);
        return TrainingSession::where($whereClause)->get();
    }

    public static function company($id) {
        return Box::findOrFail($id)->company;
    }

    public static function workouts($id, array $filters = [],$sort_col,$sort_dir) {
        $scope = null;
        if(isset($filters["scope"])) {
            $scope = $filters["scope"];
            unset($filters["scope"]);
        }

        $where_clause1 = [];
        $where_clause2 = [];

        if($scope && $scope=="general") $where_clause2 = [["box_id", null]];
        else if($scope && $scope=="custom") $where_clause1 = [["box_id", $id]];
        else {
            $where_clause1 = [["box_id", $id]];
            $where_clause2 = [["box_id", null]];
        }

        foreach($filters as $key => $filter_val) {
            if($filter_val!="null") {
                array_push($where_clause1, [$key, 'like', "%$filter_val%"]);
                array_push($where_clause2, [$key, 'like', "%$filter_val%"]);
            }
            else {
                array_push($where_clause1, [$key, null]);
                array_push($where_clause2, [$key, null]);
            }
        }
        //return TrainingWorkout::where($where_clause1)->orWhere($where_clause2)->toSql();
        return TrainingWorkout::where($where_clause1)->orWhere($where_clause2)->orderBy($sort_col,$sort_dir)->get();
    }

    /**
     * $params:
     *      => ["sort_col"]: "user_name" || "pricing" || "role_group" || "date_end" || "price"
     *      => ["sort_dir"]: "asc" || "desc"
     *      => ["subscription_status"]: 1: All || 2: Active || 3: A punto de caducar || 4: En prorroga || 5: Caducadas
     *      => ["pricing_id"]: -1: Any pricing || Specified pricing id
     *      => ["role_group"]: 1: Athletes || 2: Coaches and admins
     *      => ["user_name_like"]: string
     *      => ["renew"]: 0 o 1
     */
    public static function userSubscriptions($id, array $params = null) {
        $user_subscriptions = Box::findOrFail($id)->userSubscriptions();
        $now = Carbon::now();
        if($params) {
            //filter
            $user_subscriptions = array_filter($user_subscriptions, function($s) use(&$params, &$now) {
                if(isset($params["subscription_status"])) {
                    switch($params["subscription_status"]) {
                        case 1: //return all
                            //no fem res
                            break;
                        case 2: //only active
                            if(!$s->active) return false;
                            break;
                        case 3: //a punto de caducar
                            if(!($s->active && $now < $s->end_date && $now->diffInDays($s->end_date) < 7)) return false;
                            break;
                        case 4: //en prorroga
                            if(!($s->active && $now > $s->end_date)) return false;
                            break;
                        case 5: //caducada
                            if($s->active==true) return false;
                            break;
                    }
                }

                if(isset($params["pricing_id"]) && $params["pricing_id"]!=-1) {
                    if($params["pricing_id"]!=$s->pricing_id) return false;
                }

                if(isset($params["role_group"])) {
                    if($params["role_group"]==1 && $s->role->slug!="athlete") return false;
                    else if($params["role_group"]==2 && ($s->role->slug!="coach" && $s->role->slug!="box_admin" && $s->role->slug!="company_admin")) return false;
                }

                if(isset($params["user_name_like"]) && $params["user_name_like"]!=null) {
                    if(
                        strpos(strtolower($s->user->name), strtolower($params["user_name_like"]))===false &&
                        strpos(strtolower($s->user->surnames), strtolower($params["user_name_like"]))===false
                    ) return false;
                }

                if(isset($params["renew"])) {
                    if($params["renew"] == 1 && $s->renew!=1) return false;
                    else if($params["renew"] == 0 && $s->renew!=0) return false;
                }

                return true;
            });

            //sort
            if(isset($params["sort_col"]) && isset($params["sort_dir"])) {
                $asc = $params["sort_dir"] === "asc";
                usort($user_subscriptions, function($s1, $s2) use(&$params, &$asc) {
                    if($params["sort_col"] === "id") {
                        if($s1->id === $s2->id) return 0;
                        else return $s1->id < $s2->id ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else if($params["sort_col"] === "user_name") {
                        if($s1->user->name === $s2->user->name) {
                            if($s1->user->surnames === $s2->user->surnames) return 0;
                            else return $s1->user->surnames < $s2->user->surnames ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                        }
                        else return $s1->user->name < $s2->user->name ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else if($params["sort_col"] === "pricing") {
                        if($s1->pricing_id === $s2->pricing_id) return 0;
                        else return $s1->pricing_id < $s2->pricing_id ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else if($params["sort_col"] === "role_group") {
                        if($s1->role_id === $s2->role_id) return 0;
                        else return $s1->role_id < $s2->role_id ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else if($params["sort_col"] === "date_end") {
                        if($s1->end_date === $s2->end_date) return 0;
                        else return $s1->end_date < $s2->end_date ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else if($params["sort_col"] === "price") {
                        if($s1->price === $s2->price) return 0;
                        else return $s1->price < $s2->price ? ($asc ? -1 : 1) : ($asc ? 1 : -1);
                    }
                    else throw new \Exception("sort_col attribute value incorrect.");
                });
            }
        }

        return $user_subscriptions;
    }

    //retorna tots els coaches amb rol de coach
    public static function coachesActive($id) {
        $res = [];
        foreach(BoxRepository::userSubscriptions($id, ["subscription_status" => 2]) as $s) {
            if($s->role->isCoach()) array_push($res, $s->user);
        }
        return $res;
    }

    //retorna tots els usuaris que poden ser coaches, inclosos administradors
    public static function usersCanBeCoaches($id) {
        $res = [];
        foreach(BoxRepository::userSubscriptions($id, ["subscription_status" => 2, "role_group" => 2]) as $sub) {
            array_push($res, $sub->user);
        }
        return $res;
    }

    public static function news($id, $params = null) {
        $sort_by = "created_at";
        $sort_dir = "desc";
        $whereClause1 = [["box_id", $id]];
        $whereClause2 = [["company_id", BoxRepository::company($id)->id]];
        if($params!=null && is_array($params)) {
            if(isset($params["page_size"])) unset($params["page_size"]);
            if(isset($params["page_number"])) unset($params["page_number"]);
            if(isset($params["sort_col"])) {
                $sort_by = $params["sort_col"];
                unset($params["sort_col"]);
            }
            if(isset($params["sort_dir"])) {
                $sort_dir = $params["sort_dir"];
                unset($params["sort_dir"]);
            }
            foreach($params as $key => $val) {
                if(is_null($val)) {
                    array_push($whereClause1, [$key, null]);
                    array_push($whereClause2, [$key, null]);
                }
                else if(is_numeric($val)) {
                    array_push($whereClause1, [$key, $val]);
                    array_push($whereClause2, [$key, $val]);
                }
                else {
                    array_push($whereClause1, [$key, "like", "%$val%"]);
                    array_push($whereClause2, [$key, "like", "%$val%"]);
                }
            }
        }

        return News::where($whereClause1)
            ->orWhere($whereClause2)
            ->orderBy($sort_by, $sort_dir)
            ->get();
    }

    public static function getLastNew($id) {
        return News::where("box_id", $id)->orWhere("company_id", BoxRepository::company($id)->id)->orderByDesc("created_at")->first();
    }

    public static function generateSessions($id, Carbon $start_date, Carbon $end_date, $calendar_template_id, $training_room_id, $training_type_id = null, $coach_id = null) {
        //0. Checks inicials
        $calendar_template = CalendarTemplateRepository::find($calendar_template_id);
        $training_room = TrainingRoomRepository::find($training_room_id);
        if($calendar_template->box_id != $id  ||  $training_room->box_id != $id) throw new \Exception("Calendar Template and Training Room must be of the box provided.");
        if(sizeof(TrainingSession::where([["box_id", $id],["day", ">=", $start_date],["day", "<", $end_date],["training_room_id", $training_room_id]])->get()) > 0){
            throw new \Exception("There are already training sessions.");
        }
        if($coach_id && !UserRepository::find($coach_id)->isCoach($id)) {
            throw new \Exception("Please provide an active coach or unset the coach_id property.");
        }

        //1. Check if end_date is greater than start_date. Avoid infinite loops!
        if($start_date >= $end_date) throw new \Exception("start_date is greater than end_date. Please provide correct dates.");

        //2. Generar -- Ho fem transaccionalment
        DB::beginTransaction();
        try {
            $res = [];
            $iteration_start_date = $start_date->copy();
            $iteration_end_date = $iteration_start_date->copy()->addMinutes($calendar_template->default_duration);
            $tractat = false;
            while($iteration_end_date <= $end_date) {
                $corrected_start_date = $iteration_start_date->copy()->setTimeFromTimeString($calendar_template->start_day_time);
                $corrected_end_date = $iteration_start_date->copy()->setTimeFromTimeString($calendar_template->end_day_time);

                $bool1 = in_array($iteration_start_date->dayOfWeekIso, $calendar_template->days) ? 1 : 0; //Mirem que el dia de la setmana sigui acceptat
                $bool2 = in_array($iteration_end_date->dayOfWeekIso, $calendar_template->days) ? 1 : 0;
                $bool3 = ($corrected_start_date <= $iteration_start_date && $corrected_end_date > $iteration_start_date) ? 1 : 0; //Mirem que estigui dins els horaris normals de calendar template
                $bool4 = ($corrected_start_date <= $iteration_end_date && $corrected_end_date > $iteration_end_date) ? 1 : 0;
                $bool5 = !CalendarTemplateRepository::isExcluded($calendar_template, $iteration_start_date) ? 1 : 0; //Mirem que no estigui exclos
                $bool6 = !CalendarTemplateRepository::isExcluded($calendar_template, $iteration_end_date) ? 1 : 0;
                //var_dump("bools:", $bool1, $bool2, $bool3, $bool4, $bool5, $bool6);
                if( $bool1 && $bool2 && $bool3 && $bool4 && $bool5 && $bool6 ) {
                    //Podem crear la sessió
                    $data = [
                        "box_id" => $id,
                        "training_room_id" => $training_room_id,
                        "day" => $iteration_start_date,
                        "start_time" => $iteration_start_date,
                        "end_time" => $iteration_end_date,
                        "capacity" => $training_room->capacity
                    ];
                    if($training_type_id) $data["training_type_id"] = $training_type_id;
                    if($coach_id) $data["coach_id"] = $coach_id;
                    $session = TrainingSessionRepository::create($data);
                    array_push($res, $session);
                    $tractat = true;
                }
                else $tractat = false;

                //setejem iteradors
                $iteration_start_date = $iteration_end_date->copy();

                if($calendar_template->time_between_wods!=null && $calendar_template->time_between_wods>0 && $tractat==true)
                    $iteration_start_date->addMinutes($calendar_template->time_between_wods);

                if(!$tractat) {
                    //si no ha tractat, vol dir que estem en pausa i hem de saltar al proxim periode util
                    //pot ser la quan finalitzi la proxima exclusio, o ja passar al següent dia
                    $next_time = BoxRepository::generateWeekSessionsGetNextTime($calendar_template, $iteration_start_date);
                    if($next_time) { //hi ha una proxima hora dins el mateix dia, tipus Carbon
                        $iteration_start_date->setTimeFromTimeString($next_time->format("H:i:s"));
                    }
                    else { //s'ha de canviar de dia
                        $iteration_start_date->addDay()->setTimeFromTimeString("00:00:00");
                    }
                }

                $iteration_end_date = $iteration_start_date->copy()->addMinutes($calendar_template->default_duration);

                //var_dump("Nova iteracio", $iteration_start_date, $iteration_end_date);
            }
            //var_dump("Data fi", $end_date);
            DB::commit();
        }
        catch(\Exception $e) {
            DB::rollBack();
            throw $e;
        }

        //3. Retornar
        return $res;
    }

    public static function trainingWorkoutTypes($id) {
        return DB::table("training_workouts")
                    ->select([DB::raw("CASE WHEN category IS NOT NULL THEN category ELSE 'general' END as category"), DB::raw("CASE WHEN box_id IS NULL THEN 'general' ELSE 'custom' END as 'scope'"), DB::raw("COUNT(*) as number")])
                    ->where("box_id", $id)
                    ->orWhereNull("box_id")
                    ->groupBy("category", "scope")
                    ->orderByDesc("category")
                    ->get();
    }

    public static function sessionsBulkEditWorkouts($id, array $data) {
        if(!isset($data["training_room_id"]) || !isset($data["training_workout_id"]) || !isset($data["allDay"]) || !isset($data["datetime"])) throw new \Exception("Please provide all arguments");

        $training_room_id = $data["training_room_id"];
        $training_workout_id = $data["training_workout_id"];
        $allDay = $data["allDay"];
        $datetime = Carbon::parse($data["datetime"])->setTimezone("Utc");

        $room = TrainingRoomRepository::find($training_room_id);
        $workout = TrainingWorkoutRepository::find($training_workout_id);

        if($room->box_id != $id) throw new \Exception("Training room does not belongs to this box.");
        if($workout->box_id != null && $workout->box_id != $id) throw new \Exception("You can not work with that workout.");


        $sessions = [];
        $parametersToUpdate = ["training_workout_id" => $training_workout_id];
        if($allDay) {
            $datetime->addDay(); //perqe al ser UTC passa a ser les 22:00 i passa a ser un dia anterior
            $sessions = TrainingSession::where([
                ["box_id", $id],
                ["training_room_id", $training_room_id],
                ["training_type_id", 1], //que sigui wod
                ["day", $datetime->format("Y-m-d")]
            ])->update($parametersToUpdate);
        }
        else {
            $temp = TrainingSession::where([
                ["box_id", $id],
                ["training_room_id", $training_room_id],
                ["day", $datetime->format("Y-m-d")],
            ])->get();

            foreach($temp as $session) {
                $date_start = Carbon::parse($session->date_start);
                $date_end = Carbon::parse($session->date_end);
                if($date_start <= $datetime && $date_end >= $datetime)
                    $session->update($parametersToUpdate);
            }
        }

        return $sessions;
    }

    public static function pricings($id, array $params = []) {
        $whereClause = [];
        if(isset($params["is_public"])) $whereClause[] = ["is_public", $params["is_public"] ? 1 : 0];

        return BoxRepository::find($id)->pricing()->where($whereClause)->get();
    }

    //pre: --
    //post: Retorna la proxima hora en H:i:s . Null si no hi ha proxima hora i s'ha de passar al següent dia
    private static function generateWeekSessionsGetNextTime(CalendarTemplate $ct, Carbon $start) {
        $res = null;
        $times_carbon = [];
        $day = $start->copy()->setTimeFromTimeString($ct->start_day_time);
        array_push($times_carbon, $start);
        array_push($times_carbon, $day);
        foreach($ct->calendarTemplateExclusions as $excl) {
            $carbon = $day->copy()->setTimeFromTimeString($excl->end_time_exclusion);
            array_push($times_carbon, $carbon);
        }


        //sort de hora mes gran a hora mes petita
        rsort($times_carbon); //ordena desc

        //ja tenim els horaris de partida, ara busquem quin es el seguent
        if(($trobat_index = array_search($start, $times_carbon)) && $trobat_index>0) {
            $res = $times_carbon[$trobat_index-1];
        }

        return $res;
    }

    public static function getUserAssistance($box_id, $user_id, array $params) {
        $whereClause = [ ["ts_u.user_id", $user_id], ["ts.box_id", $box_id] ];
        if(isset($params["start_date"])) array_push($whereClause, ["ts.day", ">=", Carbon::parse($params["start_date"])]);
        if(isset($params["end_date"])) array_push($whereClause, ["ts.day", "<=", Carbon::parse($params["end_date"])]);

        return DB::table("user_training_sessions as ts_u")
            ->select(DB::raw("count(*) as count, WEEKDAY(ts.day) as weekday, tt.slug as type"))
            ->leftJoin("training_sessions as ts", "ts.id", "ts_u.training_session_id")
            ->leftJoin("training_types as tt", "tt.id", "ts.training_type_id")
            ->where($whereClause)
            ->groupBy("weekday", "tt.slug")
            ->get();
    }

    public static function getUserBoxOrders($box_id, $user_id) {
        return BoxRepository::find($box_id)->boxOrders()->where("user_id", $user_id)->orderByDesc("id")->get();
    }

    public static function lastSessionBenchmark($box_id, $user_id, array $params = []) {
        $user = User::findOrFail($user_id);

        $whereClause = [["box_id", $box_id]];
        array_push($whereClause, ["training_type_id", "<>", TrainingType::where('slug','=','open')->first()->id]);

        if(isset($params["days"]) && $params["days"]!=null) {
            $dateLimit = Carbon::now()->addDays(-($params["days"]));
            array_push($whereClause, ["day", ">=", $dateLimit]);
        }

        $ts_dirty = $user->trainingSessions()->where($whereClause)->orderByDesc("day")->get();

        $ts = [];
        foreach ($ts_dirty as $t) {
            if ($t->date_start < Carbon::now()) {
                array_push($ts,$t);
            }
        }

        $trobat = false;
        $i = 0;
        while($i<count($ts) && !$trobat) {
            $u = UserBenchmark::where("user_id","=",$user_id)
                ->where("training_session_id","=",$ts[$i]->id)
                ->get();
            if(count($u)==0) $trobat = true;
            else $i++;
        }

        if ($trobat) {
            $availableToJoin = $user->availableWorkoutSessionsId();
            $availableToLeave = $user->availableWorkoutSessionsToLeaveId();
            $workout = TrainingWorkoutRepository::find($ts[$i]->training_workout_id);

            return [
                "ts" => TrainingSessionRepository::transformWithStatus($user,$ts[$i],$availableToJoin,$availableToLeave),
                "workout" => $workout
            ];
        } else {
            return [
                "ts" => null,
                "workout" => null
            ];
        }
    }

    public static function nextTrainingSession($box_id, $user_id, array $params = []) {
        $user = User::findOrFail($user_id);
        $now = Carbon::now();

        $whereClause = [["box_id", $box_id], ["day", ">=", $now]];
        if(isset($params["days"]) && $params["days"]!=null) {
            $dateLimit = Carbon::now()->addDays($params["days"]);
            array_push($whereClause, ["day", "<=", $dateLimit]);
        }

        $ts = $user->trainingSessions()->where($whereClause)->orderBy("day", "asc")->first();
        if($ts!=null) {
            $user = UserRepository::find($user_id);
            $availableToJoin = $user->availableWorkoutSessionsId();
            $availableToLeave = $user->availableWorkoutSessionsToLeaveId();
            return TrainingSessionRepository::transformWithStatus($user,TrainingSessionRepository::find($ts['id']),$availableToJoin,$availableToLeave);
        }

        return $ts;
    }

    public static function shouldTrainMore($box_id, $user_id) {
        $daysToShouldTrainMore = 7;
        $user = User::findOrFail($user_id);
        $now = Carbon::now();

        $tss = $user->trainingSessions()->where([["box_id", $box_id], ["day", ">=", $now->copy()->addDays(-$daysToShouldTrainMore)], ["day", "<=", $now->copy()->addDays($daysToShouldTrainMore)]])->get();

        return sizeof($tss)==0;
    }

    public static function birthdays($box_id) {
        $uss = BoxRepository::userSubscriptions($box_id, ["subscription_status" => 2, "role_group" => 1]);
        $year = Carbon::now()->year;
        $uss = array_filter($uss, function($us) use(&$year) {
           return $us->user->date_of_birth->setYear($year)->isToday();
        });

        $users = array_map(function($us) {
            return $us->user;
        }, $uss);

        return $users;
    }

}
