package cz.utb.tadis.charts.controller;

import cz.utb.tadis.attacks.model.Attack;
import cz.utb.tadis.attacks.repository.AttacksRepository;
import cz.utb.tadis.auth.model.User;
import cz.utb.tadis.charts.http.ChartsFormData;
import cz.utb.tadis.charts.model.*;
import cz.utb.tadis.charts.repository.ChartsRepository;
import cz.utb.tadis.charts.repository.ChartsUsersRepository;
import cz.utb.tadis.charts.util.ChartDataUtil;
import cz.utb.tadis.common.http.ApiResponse;
import cz.utb.tadis.security.SecurityContextUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/charts")
public class ChartsController {

    @Autowired
    ChartsRepository chartsRepository;

    @Autowired
    AttacksRepository attacksRepository;

    @Autowired
    ChartsUsersRepository chartsUsersRepository;

    @Autowired
    SecurityContextUtil securityContextUtil;

    @Autowired
    ChartDataUtil chartDataUtil;

    @GetMapping("/public")
    public ResponseEntity getPublicCharts(){
        List<Chart> publicCharts = chartsRepository.findTop10ByPublicChartOrderByIdDesc(true);
        return new ResponseEntity(publicCharts, HttpStatus.OK);
    }

    @PreAuthorize("hasAnyRole('USER', 'ANALYST', 'ADMIN')")
    @GetMapping("/private")
    public ResponseEntity getPrivateCharts(){
        User user = securityContextUtil.getUserFromContext();
        List<Chart> userCharts = chartsRepository.findAllByAuthorIdOrderByIdDesc(user.getId());
        List<Chart> addedCharts = chartsUsersRepository.findAllByUserId(user.getId()).stream().map(chartUser -> chartUser.getChart()).collect(Collectors.toList());
        PrivateCharts privateCharts = new PrivateCharts(userCharts, addedCharts);
        return new ResponseEntity(privateCharts, HttpStatus.OK);
    }

    @PreAuthorize("hasAnyRole('USER', 'ANALYST', 'ADMIN')")
    @PutMapping
    @Transactional
    public ResponseEntity saveChart(@Valid @RequestBody Chart chart){
        User user = securityContextUtil.getUserFromContext();
        chart.setAuthorId(user.getId());
        if (chart.isPublicChart() && "ROLE_USER".equals(user.getRole().getName())){
            return new ResponseEntity(new ApiResponse(HttpStatus.UNAUTHORIZED, "You can't create/update public chart with this (user) privilege"), HttpStatus.UNAUTHORIZED);
        }
        if (chart.getId() != null){
            Chart currentChart = chartsRepository.findById(chart.getId()).get();
            if (currentChart.isPublicChart() && "ROLE_USER".equals(user.getRole().getName())){
                return new ResponseEntity(new ApiResponse(HttpStatus.UNAUTHORIZED, "You can't create/update public chart with this (user) privilege"), HttpStatus.UNAUTHORIZED);
            }
            if (currentChart.isPublicChart() && !chart.isPublicChart()){
                chartsUsersRepository.deleteByChartId(chart.getId());
            }
        }
        chart.setAuthorId(user.getId());
        Chart savedChart = chartsRepository.save(chart);
        return new ResponseEntity(savedChart, HttpStatus.OK);
    }

    @PreAuthorize("hasAnyRole('USER', 'ANALYST', 'ADMIN')")
    @DeleteMapping("/{id}")
    @Transactional
    public ResponseEntity deleteChart(@PathVariable("id") Long id) {
        User user = securityContextUtil.getUserFromContext();
        Chart chart = this.chartsRepository.findById(id).get();
        if ("ROLE_USER".equals(user.getRole().getName()) && !user.getId().equals(chart.getAuthorId()))
            return new ResponseEntity(new ApiResponse(HttpStatus.UNAUTHORIZED, "You can delete only your own charts with this (user) privilege."), HttpStatus.UNAUTHORIZED);
        else {
            chartsUsersRepository.deleteByChartId(chart.getId());
            chartsRepository.deleteById(id);
        }
        return new ResponseEntity(HttpStatus.OK);
    }

    @PreAuthorize("hasAnyRole('USER', 'ANALYST', 'ADMIN')")
    @PostMapping("/private/add/{id}")
    @Transactional
    public ResponseEntity addToPrivate(@PathVariable("id") Long chartId){
        User user = securityContextUtil.getUserFromContext();
        Chart chart = chartsRepository.findById(chartId).get();
        Optional<ChartUser> optionalChartUser = chartsUsersRepository.findByUserIdAndChartId(user.getId(), chart.getId());
        if (optionalChartUser.isPresent()){
            return new ResponseEntity(
                    new ApiResponse(HttpStatus.CONFLICT, "This user already has this chart pinned"),
                    HttpStatus.CONFLICT
            );
        }
        if (user.getId().equals(chart.getAuthorId())){
            return new ResponseEntity(
                    new ApiResponse(HttpStatus.CONFLICT, "You can't pin your own chart"),
                    HttpStatus.CONFLICT
            );
        }
        ChartUser chartUser = chartsUsersRepository.save(new ChartUser(user.getId(), chart));
        return new ResponseEntity(chartUser, HttpStatus.OK);
    }

    @PreAuthorize("hasAnyRole('USER', 'ANALYST', 'ADMIN')")
    @DeleteMapping("/private/remove/{id}")
    @Transactional
    public ResponseEntity removeFromPrivate(@PathVariable("id") Long chartId){
        User user = securityContextUtil.getUserFromContext();
        chartsUsersRepository.deleteByUserIdAndChartId(user.getId(), chartId);
        return new ResponseEntity(HttpStatus.OK);
    }

    @GetMapping("/form")
    public ResponseEntity getFormData(){
        Optional<Attack> newestAttack =  attacksRepository.findFirstByOrderByDateDesc();
        Optional<Attack> oldestAttack = attacksRepository.findFirstByOrderByDateAsc();
        ChartsFormData formData = new ChartsFormData(ChartAxis.values(), ChartType.values(), oldestAttack.isPresent() ? oldestAttack.get().getDate() : null, newestAttack.isPresent() ? newestAttack.get().getDate() : null);
        return new ResponseEntity(formData, HttpStatus.OK);
    }

    @GetMapping("/chartMeta/{id}")
    public ResponseEntity getChartMeta(@PathVariable("id") Long chartId){
        Optional<Chart> chart = chartsRepository.findById(chartId);
        if (chart.isPresent()){
            if (chart.get().isPublicChart() || chart.get().getAuthorId().equals(getLoggedUserId()))
                return new ResponseEntity(chart, HttpStatus.OK);
            else
                return new ResponseEntity(new ApiResponse(HttpStatus.UNAUTHORIZED, "You are not allowed to retrieve data for requested chart"), HttpStatus.UNAUTHORIZED);
        }
        return new ResponseEntity(new ApiResponse(HttpStatus.NOT_FOUND, "Chart with id: "+chartId+" not found."), HttpStatus.NOT_FOUND);
    }

    @GetMapping("/chart/{id}")
    public ResponseEntity getChart(@PathVariable("id") Long chartId){
        Optional<Chart> chart = chartsRepository.findById(chartId);
        if (chart.isPresent()){
            if (chart.get().isPublicChart() || chart.get().getAuthorId().equals(getLoggedUserId()))
                return new ResponseEntity(chartDataUtil.getChartData(chart.get()), HttpStatus.OK);
            else
                return new ResponseEntity(new ApiResponse(HttpStatus.UNAUTHORIZED, "You are not allowed to retrieve data for requested chart"), HttpStatus.UNAUTHORIZED);
        }
        return new ResponseEntity(new ApiResponse(HttpStatus.NOT_FOUND, "Chart with id: "+chartId+" not found."), HttpStatus.NOT_FOUND);
    }

    private Long getLoggedUserId(){
        Long loggedUserId;
        try{
            User user = securityContextUtil.getUserFromContext();
            loggedUserId = user.getId();
        }
        catch(NoSuchElementException e){
            loggedUserId = (long) -1;
        }
        return loggedUserId;
    }
}
