/*
 * File: src/main/java/purchase/controller/GRNController.java
 * Description: This controller handles all API requests for managing Goods Received Notes (GRN) and GRN items.
 * It provides endpoints to create, retrieve, update, and delete GRNs, as well as retrieve product and purchase details
 * using external services. This class also includes Swagger documentation for each API endpoint.
*/

package com.nebula.erp.purchase.controller;

import com.nebula.erp.purchase.document.GRNSwagger;
import com.nebula.erp.purchase.model.GRN;
import com.nebula.erp.purchase.requestmodel.GRNRequest;
import com.nebula.erp.purchase.service.GRNService;
import com.nebula.erp.purchase.utility.*;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.HttpClientErrorException;
import org.springframework.web.client.HttpServerErrorException;
import org.springframework.web.client.RestTemplate;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/grn")
@Tag(name = "GRN APIs", description = "API for managing grn and grn Items")
@GRNSwagger.GlobalErrorResponse
public class GRNController {

    @Autowired
    private GRNService grnService;

    @Autowired
    private RestTemplate restTemplate;

    @Value("${product.api}")
    private String productAPI;

    @Value("${purchase.get.api}")
    private String purchaseAPI;
   
    @Value("${organization.api}")
    private String organizationAPI;

    @Autowired
    private JwtRequestUtils jwtRequestUtils;

    @Autowired
    private PermissionHelper permissionHelper;

    @Autowired
    private CreateLogger createLogger;

    private static final String path = "/grn";

    @GRNSwagger.CreateGRNOperation
    @PostMapping
    public ResponseEntity<ApiResponseStructure<GRN>> createGRN(@RequestBody GRNRequest grnRequest) {
        try {
            // Check user permissions
            if (!permissionHelper.hasPermission("create-purchase")) {
                createLogger.createLogger("error", path, "POST", "Forbidden: You do not have the required permission. Please contact the administration.", "validation");
                return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ApiResponseStructure<>("error", HttpStatus.FORBIDDEN.value(), "Forbidden: You do not have the required permission. Please contact the administration.", null));
            }

            GRN createdGRN = grnService.createGRN(grnRequest);
            ApiResponseStructure<GRN> response = new ApiResponseStructure<>("success", HttpStatus.CREATED.value(), "GRN created successfully", createdGRN);
            createLogger.createLogger("application", path, "POST", "GRN created successfully", "");
            return ResponseEntity.status(HttpStatus.CREATED).body(response);
        } catch (Exception e) {
            createLogger.createLogger("error", path, "POST", e.getMessage(), "runtime");
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiResponseStructure<>("error", HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
        }
    }

    @GRNSwagger.GetGRNByIdOperation
    @GetMapping("/{id}")
    public ResponseEntity<ApiResponseStructure<Map<String, Object>>> getGRNById(@PathVariable Long id) {
        try {
            // Check user permissions
            if (!permissionHelper.hasPermission("view-purchase")) {
                createLogger.createLogger("error", path, "GET", "Forbidden: You do not have the required permission. Please contact the administration.", "validation");
                return ResponseEntity.status(HttpStatus.FORBIDDEN)
                        .body(new ApiResponseStructure<>("error", HttpStatus.FORBIDDEN.value(), "Forbidden: You do not have the required permission. Please contact the administration.", null));
            }

            HttpHeaders authHeader = jwtRequestUtils.getAuthorizationHeaders();
            HttpEntity<Void> entity = new HttpEntity<>(authHeader);

            Optional<GRN> grnOptional = grnService.getGRN(id);
            if (grnOptional.isPresent()) {
                GRN grnData = grnOptional.get();

                // Create a map to hold sales data
                Map<String, Object> data = prepareGrnResponse(grnData);

                // Define columns for grn items
                Map<String, String> grnItemColumns = new LinkedHashMap<>();
                grnItemColumns.put("id", "ID");
                grnItemColumns.put("product_id", "PRODUCT ID");
                grnItemColumns.put("product_name", "PRODUCT NAME");
                grnItemColumns.put("brand_id", "BRAND ID");
                grnItemColumns.put("brand_name", "BRAND NAME");
                grnItemColumns.put("category_id", "CATEGORY ID");
                grnItemColumns.put("category_name", "CATEGORY NAME");
                grnItemColumns.put("tax_rate", "TAX RATE");
                grnItemColumns.put("hsn_code", "HSN CODE");
                grnItemColumns.put("quantity", "QUANTITY");
                grnItemColumns.put("unit_price", "UNIT PRICE");
                grnItemColumns.put("total_price", "TOTAL PRICE");
                grnItemColumns.put("manufacture_date", "MANUFACTURE DATE");
                grnItemColumns.put("expiration_date", "EXPIRATION DATE");
                grnItemColumns.put("batch_code", "BATCH CODE");
                grnItemColumns.put("tax_id", "TAX ID");
                data.put("columns", grnItemColumns);

                ApiResponseStructure<Map<String, Object>> response = new ApiResponseStructure<>("success", HttpStatus.OK.value(), "GRN retrieved successfully", data);
                createLogger.createLogger("application", path, "GET", "GRN retrieved successfully", "");
                return ResponseEntity.ok(response);
            } else {
                createLogger.createLogger("error", path, "GET", "GRN not found", "runtime");
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(new ApiResponseStructure<>("error", HttpStatus.NOT_FOUND.value(), "GRN not found", null));
            }
        } catch (Exception e) {
            createLogger.createLogger("error", path, "GET", e.getMessage(), "runtime");
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiResponseStructure<>("error", HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
        }
    }

    @GRNSwagger.GetAllGRNsOperation
    @GetMapping
    public ResponseEntity<ApiResponseStructure<Map<String, Object>>> getAllGRN(
            @RequestParam(value = "page", defaultValue = "1") int page,
            @RequestParam(value = "size", defaultValue = "10") int size,
            @RequestParam(value = "purchase_return", required = false) Boolean purchase_return,
            @RequestParam(value = "purchase_order_id", required = false) Integer purchaseOrderId,
            @RequestParam(value = "search", required = false) String search){
        try {
            // Check user permissions
            if (!permissionHelper.hasPermission("view-purchase")) {
                createLogger.createLogger("error", path, "GET", "Forbidden: You do not have the required permission. Please contact the administration.", "validation");
                return ResponseEntity.status(HttpStatus.FORBIDDEN)
                        .body(new ApiResponseStructure<>("error", HttpStatus.FORBIDDEN.value(), "Forbidden: You do not have the required permission. Please contact the administration.", null));
            }

            Page<GRN> grnPage = grnService.getAllGRN(page, size, purchase_return, purchaseOrderId, search);

            // Prepare pagination data
            Map<String, Object> pagination = new LinkedHashMap<>();
            pagination.put("current_page", grnPage.getNumber() + 1);
            pagination.put("per_page", grnPage.getSize());
            pagination.put("total", grnPage.getTotalElements());
            pagination.put("last_page", grnPage.getTotalPages());
            pagination.put("next_page_url", grnPage.hasNext() ? "/api/grn?page=" + (page + 1) + "&size=" + size : null);
            pagination.put("prev_page_url", grnPage.hasPrevious() ? "/api/grn?page=" + (page - 1) + "&size=" + size : null);

            // Define columns for grn
            Map<String, String> columns = new LinkedHashMap<>();
            columns.put("id", "ID");
            columns.put("purchase_order_id", "PURCHASE ORDER ID");
            columns.put("supplier_id", "SUPPLIER ID");
            columns.put("supplier_name", "SUPPLIER NAME");
            columns.put("date", "DATE");
            columns.put("invoice_number", "INVOICE NUMBER");
            columns.put("product_id", "PRODUCT ID");
            columns.put("quantity", "QUANTITY");
            columns.put("hsn_code", "HSN CODE");
            columns.put("unit_price", "UNIT PRICE");
            columns.put("total_price", "TOTAL PRICE");
            columns.put("expiration_date", "EXPIRATION DATE");
            columns.put("tax_id", "TAX ID");
            columns.put("created_by", "CREATED BY");
            columns.put("created_at", "CREATED AT");

            // Prepare data for response
            List<Map<String, Object>> grnItems = grnPage.getContent().stream()
                    .map(this::prepareGrnResponse)
                    .filter(Objects::nonNull)  // Exclude prescriptions with no items
                    .collect(Collectors.toList());

            // Prepare data for response
            Map<String, Object> data = new LinkedHashMap<>();
            data.put("pagination", pagination);
            data.put("items", grnItems);
            data.put("columns", columns);

            // Create and return ApiResponseStructure object
            ApiResponseStructure<Map<String, Object>> response = new ApiResponseStructure<>("success", HttpStatus.OK.value(), "GRN retrieved successfully", data);
            createLogger.createLogger("application", path, "GET", "GRN retrieved successfully", "");
            return ResponseEntity.ok(response);
        } catch (Exception e) {
            createLogger.createLogger("error", path, "GET", e.getMessage(), "runtime");
            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ApiResponseStructure<>("error", HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
        }
    }

    // Helper method to prepare response data for a grn
    private Map<String, Object> prepareGrnResponse(GRN grnData) {
        // Extract Authorization headers from the request
        HttpHeaders headers = jwtRequestUtils.getAuthorizationHeaders();

        // Create an HttpEntity with the extracted headers
        HttpEntity<String> entity = new HttpEntity<>(headers);

        Map<String, Object> data = new LinkedHashMap<>();
        data.put("id", grnData.getId());
        data.put("purchase_order_id", grnData.getPurchase().getId());

        try {
            Map<String, Object> purchasedata = restTemplate.exchange(purchaseAPI  + grnData.getPurchase().getId(), HttpMethod.GET, entity, Map.class).getBody();
            Map<String, Object> purchaseItem = (Map<String, Object>) purchasedata.get("data");

            if (purchaseItem != null && !purchaseItem.isEmpty()) {
                // Add brand and category details if available
                data.put("purchase_date", String.valueOf(purchaseItem.get("date")));
                data.put("purchase_status", purchaseItem.get("status"));
                data.put("purchase_remarks", purchaseItem.get("remarks"));
                data.put("purchase_created_by", purchaseItem.get("created_by"));
                data.put("purchase_created_at", String.valueOf(purchaseItem.get("created_at")));
                data.put("purchase_updated_at", purchaseItem.get("updated_at"));
            }
        } catch (HttpClientErrorException | HttpServerErrorException ex) {
            throw ex;
        }

        data.put("supplier_id", grnData.getSupplier().getId());
        data.put("supplier_name", grnData.getSupplier().getSupplier_name());
        data.put("date", String.valueOf(grnData.getDate()));
        data.put("invoice_number", grnData.getInvoice_number());
        data.put("created_by", grnData.getCreated_by());
        data.put("created_at", String.valueOf(grnData.getCreated_at()));

        // Map grn items
        List<Map<String, Object>> grnItemsData = grnData.getGrn_items().stream()
                .map(item -> {
                    Map<String, Object> itemData = new LinkedHashMap<>();
                    itemData.put("id", item.getId());
                    itemData.put("product_id", item.getProduct_id());

                    try {
                        Map<String, Object> productData = restTemplate.exchange(productAPI  + item.getProduct_id(), HttpMethod.GET, entity, Map.class).getBody();
                        List<Map<String, Object>> items = (List<Map<String, Object>>) ((Map<String, Object>) productData.get("data")).get("items");

                        if (items != null && !items.isEmpty()) {
                            // Extract the first product's details
                            Map<String, Object> product = items.get(0);

                            // Add brand and category details if available
                            itemData.put("product_name", product.get("name"));
                            itemData.put("brand_id", product.get("brand_id"));
                            itemData.put("brand_name", product.get("brand_name"));
                            itemData.put("category_id", product.get("category_id"));
                            itemData.put("category_name", product.get("category_name"));
                            itemData.put("unit_price", product.get("selling_price"));
                            itemData.put("tax_id", product.get("tax_id"));
                            itemData.put("tax_rate", product.get("tax_rate"));
                            itemData.put("hsn_code", product.get("hsn_code"));
                        }
                    } catch (HttpClientErrorException | HttpServerErrorException ex) {
                        throw ex;
                    }

                    itemData.put("quantity", item.getQuantity());
                    itemData.put("unit_price", item.getUnit_price());
                    itemData.put("total_price", MoneyUtils.truncateToTwoDecimals(item.getTotal_price()));
                    itemData.put("manufacture_date", String.valueOf(item.getManufacture_date()));
                    itemData.put("expiration_date", String.valueOf(item.getExpiration_date()));
                    itemData.put("batch_code", item.getBatch_code());
                    itemData.put("tax_id", item.getTax_id());
                    return itemData;
                }).collect(Collectors.toList());

        data.put("grn_items", grnItemsData);

        return data;
    }
    @GRNSwagger.GetGRNByIdOperation
@GetMapping("/export/{id}")
public ResponseEntity<ApiResponseStructure<Map<String, Object>>> exportGRNById(@PathVariable Long id) {
    try {
        // Check user permissions
        if (!permissionHelper.hasPermission("view-purchase")) {
            createLogger.createLogger("error", path, "GET", "Forbidden: You do not have the required permission. Please contact the administration.", "validation");
            return ResponseEntity.status(HttpStatus.FORBIDDEN)
                    .body(new ApiResponseStructure<>("error", HttpStatus.FORBIDDEN.value(),
                            "Forbidden: You do not have the required permission. Please contact the administration.", null));
        }

        String tenantName = jwtRequestUtils.getTenantName();

        Optional<GRN> grnOptional = grnService.getGRN(id);
        if (grnOptional.isPresent()) {
            GRN grnData = grnOptional.get();

            // Check if the tenant name matches
            if (!grnData.getTenant().equals(tenantName)) {
                createLogger.createLogger("error", path, "GET", "Unauthorized to view this GRN; tenant mismatch", "validation");
                return ResponseEntity.status(HttpStatus.FORBIDDEN)
                        .body(new ApiResponseStructure<>("error", HttpStatus.FORBIDDEN.value(),
                                "Unauthorized to view this GRN; tenant mismatch", null));
            }

            Map<String, Object> data = prepareGrnExportResponse(grnData);

            // Columns for grn items
            Map<String, String> grnItemColumns = new LinkedHashMap<>();
            grnItemColumns.put("id", "ID");
            grnItemColumns.put("product_id", "PRODUCT ID");
            grnItemColumns.put("product_name", "PRODUCT NAME");
            grnItemColumns.put("brand_id", "BRAND ID");
            grnItemColumns.put("brand_name", "BRAND NAME");
            grnItemColumns.put("category_id", "CATEGORY ID");
            grnItemColumns.put("category_name", "CATEGORY NAME");
            grnItemColumns.put("tax_type", "TAX TYPE");
            grnItemColumns.put("tax_rate", "TAX RATE");
            grnItemColumns.put("hsn_code", "HSN CODE");
            grnItemColumns.put("quantity", "QUANTITY");
            grnItemColumns.put("unit_price", "UNIT PRICE");
            grnItemColumns.put("tax_amount", "TAX AMOUNT");
            grnItemColumns.put("total_price", "TOTAL PRICE");
            grnItemColumns.put("manufacture_date", "MANUFACTURE DATE");
            grnItemColumns.put("expiration_date", "EXPIRATION DATE");
            grnItemColumns.put("batch_code", "BATCH CODE");
            data.put("columns", grnItemColumns);

            ApiResponseStructure<Map<String, Object>> response = new ApiResponseStructure<>(
                    "success", HttpStatus.OK.value(), "GRN export retrieved successfully", data);
            createLogger.createLogger("application", path, "GET", "GRN export retrieved successfully", "");
            return ResponseEntity.ok(response);

        } else {
            createLogger.createLogger("error", path, "GET", "GRN not found", "runtime");
            return ResponseEntity.status(HttpStatus.NOT_FOUND)
                    .body(new ApiResponseStructure<>("error", HttpStatus.NOT_FOUND.value(), "GRN not found", null));
        }
    } catch (Exception e) {
        createLogger.createLogger("error", path, "GET", e.getMessage(), "runtime");
        return ResponseEntity.status(HttpStatus.BAD_REQUEST)
                .body(new ApiResponseStructure<>("error", HttpStatus.BAD_REQUEST.value(), e.getMessage(), null));
    }
}
private Map<String, Object> prepareGrnExportResponse(GRN grnData) {
    // Extract Authorization headers
    HttpHeaders headers = jwtRequestUtils.getAuthorizationHeaders();
    HttpEntity<String> entity = new HttpEntity<>(headers);

    Map<String, Object> data = new LinkedHashMap<>();

    // ── GRN Header Fields ────────────────────────────────────────
    data.put("id",             grnData.getId());
    data.put("purchase_order_id", grnData.getPurchase().getId());
    data.put("invoice_number", grnData.getInvoice_number());
    data.put("date",           String.valueOf(grnData.getDate()));
    data.put("created_by",     grnData.getCreated_by());
    data.put("created_at",     String.valueOf(grnData.getCreated_at()));

    // ── Supplier ─────────────────────────────────────────────────
    data.put("supplier_id",    grnData.getSupplier().getId());
    data.put("supplier_name",  grnData.getSupplier().getSupplier_name());
    data.put("supplier",       grnData.getSupplier());

    // ── Linked Purchase Details ──────────────────────────────────
    try {
        Map<String, Object> purchaseResponse = restTemplate
                .exchange(purchaseAPI + grnData.getPurchase().getId(), HttpMethod.GET, entity, Map.class)
                .getBody();
        Map<String, Object> purchaseItem = (Map<String, Object>) purchaseResponse.get("data");

        if (purchaseItem != null) {
            data.put("purchase_date",       String.valueOf(purchaseItem.get("date")));
            data.put("purchase_status",     purchaseItem.get("status"));
            data.put("purchase_remarks",    purchaseItem.get("remarks"));
            data.put("purchase_created_at", String.valueOf(purchaseItem.get("created_at")));
        }
    } catch (HttpClientErrorException | HttpServerErrorException ex) {
        throw ex;
    }

    // ── GRN Items with full product + tax + price details ────────
    final double[] totalTaxAmount   = {0};
    final double[] totalSubtotal    = {0};
    final double[] totalGrandAmount = {0};

    List<Map<String, Object>> grnItemsData = grnData.getGrn_items().stream()
            .map(item -> {
                Map<String, Object> itemData = new LinkedHashMap<>();
                itemData.put("id",         item.getId());
                itemData.put("product_id", item.getProduct_id());

                try {
                    Map<String, Object> productData = restTemplate
                            .exchange(productAPI + item.getProduct_id(), HttpMethod.GET, entity, Map.class)
                            .getBody();
                    List<Map<String, Object>> products = (List<Map<String, Object>>)
                            ((Map<String, Object>) productData.get("data")).get("items");

                    if (products != null && !products.isEmpty()) {
                        Map<String, Object> product = products.get(0);
                        itemData.put("product_name",   product.get("name"));
                        itemData.put("brand_id",       product.get("brand_id"));
                        itemData.put("brand_name",     product.get("brand_name"));
                        itemData.put("category_id",    product.get("category_id"));
                        itemData.put("category_name",  product.get("category_name"));
                        itemData.put("hsn_code",       product.get("hsn_code"));
                        itemData.put("tax_id",         product.get("tax_id"));
                        itemData.put("tax_type",       product.get("tax_type"));
                        itemData.put("tax_rate",       product.get("tax_rate"));
                    }
                } catch (HttpClientErrorException | HttpServerErrorException ex) {
                    throw ex;
                }

                // ── Price Calculations ───────────────────────────
                double unitPrice  = item.getUnit_price() != null  ? item.getUnit_price()  : 0.0;
                double quantity   = item.getQuantity()   != null  ? item.getQuantity()    : 0.0;
                double taxRate    = itemData.get("tax_rate") != null
                        ? Double.parseDouble(String.valueOf(itemData.get("tax_rate"))) : 0.0;

                double lineSubtotal = MoneyUtils.truncateToTwoDecimals(unitPrice * quantity);
                double taxAmount    = MoneyUtils.truncateToTwoDecimals(lineSubtotal * taxRate / 100);
                double totalPrice   = MoneyUtils.truncateToTwoDecimals(item.getTotal_price() != null
                        ? item.getTotal_price() : lineSubtotal + taxAmount);

                itemData.put("unit_price",    unitPrice);
                itemData.put("quantity",      (int) quantity);
                itemData.put("tax_amount",    taxAmount);
                itemData.put("total_price",   totalPrice);
                itemData.put("manufacture_date", String.valueOf(item.getManufacture_date()));
                itemData.put("expiration_date",  String.valueOf(item.getExpiration_date()));
                itemData.put("batch_code",    item.getBatch_code());

                // ── Accumulate totals ────────────────────────────
                totalSubtotal[0]    += lineSubtotal;
                totalTaxAmount[0]   += taxAmount;
                totalGrandAmount[0] += totalPrice;

                return itemData;
            })
            .collect(Collectors.toList());

    data.put("grn_items",         grnItemsData);
    data.put("total_subtotal",    MoneyUtils.truncateToTwoDecimals(totalSubtotal[0]));
    data.put("total_tax_amount",  MoneyUtils.truncateToTwoDecimals(totalTaxAmount[0]));
    data.put("total_amount",      MoneyUtils.truncateToTwoDecimals(totalGrandAmount[0]));

    // ── Organization ─────────────────────────────────────────────
    try {
        HttpHeaders authHeaders = jwtRequestUtils.getAuthorizationHeaders();
        HttpEntity<Void> authEntity = new HttpEntity<>(authHeaders);
        Map<String, Object> organizationData = restTemplate
                .exchange(organizationAPI, HttpMethod.GET, authEntity, Map.class)
                .getBody();
        Map<String, Object> organizationItem = (Map<String, Object>) organizationData.get("data");
        if (organizationItem != null) {
            data.put("organization", organizationItem);
        }
    } catch (HttpClientErrorException | HttpServerErrorException ex) {
        throw ex;
    }

    return data;
}

}