package de.votian.services.controller;

import de.votian.services.dto.InvoiceCustomerSummaryDto;
import de.votian.services.dto.InvoiceJobRowDto;
import de.votian.services.dto.UserSessionDto;
import de.votian.services.security.AccessControlService;
import de.votian.services.service.InvoiceExportService;
import de.votian.services.service.JobService;
import de.votian.services.service.ViewDataService;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.util.List;
import java.util.Objects;

@RestController
@RequestMapping("/api/invoices")
public class InvoiceController {

    private final ViewDataService viewDataService;
    private final InvoiceExportService invoiceExportService;
    private final JobService jobService;
    private final AccessControlService accessControlService;

    public InvoiceController(ViewDataService viewDataService,
                             InvoiceExportService invoiceExportService,
                             JobService jobService,
                             AccessControlService accessControlService) {
        this.viewDataService = viewDataService;
        this.invoiceExportService = invoiceExportService;
        this.jobService = jobService;
        this.accessControlService = accessControlService;
    }

    @GetMapping("/customers")
    public ResponseEntity<List<InvoiceCustomerSummaryDto>> getCustomers(
            @RequestParam Long hqId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.canViewInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (accessControlService.isCustomerUser(user)) {
            return ResponseEntity.ok(viewDataService.findInvoiceCustomers(
                    user.getHeadquartersId(),
                    user.getCustomerId(),
                    user.getAccessibleCostCenterIds(),
                    from,
                    to
            ));
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findInvoiceCustomers(hqId, from, to));
    }

    @GetMapping("/items")
    public ResponseEntity<List<InvoiceJobRowDto>> getItems(
            @RequestParam Long hqId,
            @RequestParam Long customerId,
            @RequestParam(required = false) Long costCenterId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.canViewInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        if (accessControlService.isCustomerUser(user)) {
            return ResponseEntity.ok(viewDataService.findInvoiceItems(
                    user.getHeadquartersId(),
                    user.getCustomerId(),
                    costCenterId,
                    user.getAccessibleCostCenterIds(),
                    from,
                    to
            ));
        }
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findInvoiceItems(hqId, customerId, costCenterId, from, to));
    }

    @GetMapping("/courier-items")
    public ResponseEntity<List<InvoiceJobRowDto>> getCourierItems(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            @RequestParam(defaultValue = "finished") String mode,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.isCourierUser(user) || !accessControlService.canViewInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        return ResponseEntity.ok(viewDataService.findCourierInvoiceItems(
                user.getCourierId(),
                from,
                to,
                isFinishedMode(mode)
        ));
    }

    @GetMapping("/export/csv")
    public ResponseEntity<byte[]> exportCsv(
            @RequestParam Long hqId,
            @RequestParam Long customerId,
            @RequestParam(required = false) Long costCenterId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.canExportInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        List<InvoiceJobRowDto> rows = accessControlService.isCustomerUser(user)
                ? viewDataService.findInvoiceItems(user.getHeadquartersId(), user.getCustomerId(), costCenterId, user.getAccessibleCostCenterIds(), from, to)
                : loadInvoiceItemsForHeadquarters(user, hqId, customerId, costCenterId, from, to);
        if (rows == null) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.markExported(rows.stream().map(InvoiceJobRowDto::getJobId).toList());
        return fileResponse(
                buildInvoiceFilename("invoices", customerId, from, to, "csv"),
                "text/csv; charset=UTF-8",
                invoiceExportService.createCsv("Rechnungsausgabe", rows)
        );
    }

    @GetMapping("/export/pdf")
    public ResponseEntity<byte[]> exportPdf(
            @RequestParam Long hqId,
            @RequestParam Long customerId,
            @RequestParam(required = false) Long costCenterId,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.canExportInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        List<InvoiceJobRowDto> rows = accessControlService.isCustomerUser(user)
                ? viewDataService.findInvoiceItems(user.getHeadquartersId(), user.getCustomerId(), costCenterId, user.getAccessibleCostCenterIds(), from, to)
                : loadInvoiceItemsForHeadquarters(user, hqId, customerId, costCenterId, from, to);
        if (rows == null) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        jobService.markExported(rows.stream().map(InvoiceJobRowDto::getJobId).toList());
        return fileResponse(
                buildInvoiceFilename("invoices", customerId, from, to, "pdf"),
                MediaType.APPLICATION_PDF_VALUE,
                invoiceExportService.createPdf("Rechnungsausgabe", rows)
        );
    }

    @GetMapping("/courier-export/csv")
    public ResponseEntity<byte[]> exportCourierCsv(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            @RequestParam(defaultValue = "finished") String mode,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.isCourierUser(user) || !accessControlService.canExportInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        List<InvoiceJobRowDto> rows = viewDataService.findCourierInvoiceItems(
                user.getCourierId(),
                from,
                to,
                isFinishedMode(mode)
        );
        jobService.markExported(rows.stream().map(InvoiceJobRowDto::getJobId).toList());
        return fileResponse(
                buildInvoiceFilename("courier_jobs", user.getCourierId(), from, to, "csv"),
                "text/csv; charset=UTF-8",
                invoiceExportService.createCsv("Kurierauftraege", rows)
        );
    }

    @GetMapping("/courier-export/pdf")
    public ResponseEntity<byte[]> exportCourierPdf(
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
            @RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to,
            @RequestParam(defaultValue = "finished") String mode,
            Authentication authentication) {
        UserSessionDto user = accessControlService.sessionUser(authentication);
        if (!accessControlService.isCourierUser(user) || !accessControlService.canExportInvoices(user)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
        }
        List<InvoiceJobRowDto> rows = viewDataService.findCourierInvoiceItems(
                user.getCourierId(),
                from,
                to,
                isFinishedMode(mode)
        );
        jobService.markExported(rows.stream().map(InvoiceJobRowDto::getJobId).toList());
        return fileResponse(
                buildInvoiceFilename("courier_jobs", user.getCourierId(), from, to, "pdf"),
                MediaType.APPLICATION_PDF_VALUE,
                invoiceExportService.createPdf("Kurierauftraege", rows)
        );
    }

    private List<InvoiceJobRowDto> loadInvoiceItemsForHeadquarters(UserSessionDto user,
                                                                   Long hqId,
                                                                   Long customerId,
                                                                   Long costCenterId,
                                                                   LocalDate from,
                                                                   LocalDate to) {
        if (!accessControlService.canAccessHeadquarters(user, hqId)) {
            return null;
        }
        return viewDataService.findInvoiceItems(hqId, customerId, costCenterId, from, to);
    }

    private ResponseEntity<byte[]> fileResponse(String filename, String contentType, byte[] body) {
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
                .header(HttpHeaders.CACHE_CONTROL, "no-store")
                .contentType(Objects.requireNonNull(MediaType.parseMediaType(Objects.requireNonNull(contentType))))
                .body(body);
    }

    private String buildInvoiceFilename(String prefix, Long referenceId, LocalDate from, LocalDate to, String extension) {
        return prefix + "_" + (referenceId != null ? referenceId : "all") + "_" + from + "_" + to + "." + extension;
    }

    private boolean isFinishedMode(String mode) {
        return mode == null || !"running".equalsIgnoreCase(mode);
    }
}
