/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.quarkus.component.beanio.it;

import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import io.quarkus.test.junit.QuarkusTest;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import jakarta.json.bind.JsonbBuilder;
import org.apache.camel.quarkus.component.beanio.it.model.Employee;
import org.apache.camel.quarkus.component.beanio.it.model.EmployeeAnnotated;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static org.hamcrest.Matchers.is;
import static org.junit.jupiter.api.Assertions.assertEquals;

@QuarkusTest
class BeanioTest {
    private final SimpleDateFormat formatter = new SimpleDateFormat(BeanioResource.DATA_FORMAT);

    @ParameterizedTest
    @ValueSource(strings = { "CSV", "DELIMITED", "FIXEDLENGTH", "XML", })
    void marshal(String type) throws Exception {
        List<Employee> employees = new ArrayList<>();
        Employee one = new Employee();
        one.setFirstName("Joe");
        one.setLastName("Smith");
        one.setTitle("Developer");
        one.setSalary(75000);
        one.setHireDate(formatter.parse("2009-11-01"));
        employees.add(one);

        Employee two = new Employee();
        two.setFirstName("Jane");
        two.setLastName("Doe");
        two.setTitle("Architect");
        two.setSalary(80000);
        two.setHireDate(formatter.parse("2008-01-15"));
        employees.add(two);

        Employee three = new Employee();
        three.setFirstName("Jon");
        three.setLastName("Anderson");
        three.setTitle("Manager");
        three.setSalary(85000);
        three.setHireDate(formatter.parse("2007-03-18"));
        employees.add(three);

        String expected = IOUtils.toString(
                BeanioTest.class.getResourceAsStream("/employees-%s.txt".formatted(type.toLowerCase())),
                StandardCharsets.UTF_8);
        if (type.equals("XML")) {
            // Clean up XML to match what is returned from beanio
            expected = expected.replaceAll("(?s)<!--.*?-->", "").replaceAll(">[\\s\r\n]*<", "><");
        }

        String result = RestAssured.given()
                .queryParam("type", type)
                .contentType(ContentType.JSON)
                .body(JsonbBuilder.create().toJson(employees))
                .post("/beanio/marshal")
                .then()
                .statusCode(200)
                .extract()
                .body()
                .asString();

        assertEquals(expected.trim(), result.trim());
    }

    @ParameterizedTest
    @ValueSource(strings = { "CSV", "DELIMITED", "FIXEDLENGTH", "XML" })
    void unmarshal(String type) {
        RestAssured.given()
                .queryParam("type", type)
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/employees-%s.txt".formatted(type.toLowerCase())))
                .post("/beanio/unmarshal")
                .then()
                .statusCode(200)
                .body(
                        "[0].firstName", is("Joe"),
                        "[0].lastName", is("Smith"),
                        "[0].title", is("Developer"),
                        "[0].salary", is(75000),
                        "[0].hireDate", is("2009-11-01"),
                        "[1].firstName", is("Jane"),
                        "[1].lastName", is("Doe"),
                        "[1].title", is("Architect"),
                        "[1].salary", is(80000),
                        "[1].hireDate", is("2008-01-15"),
                        "[2].firstName", is("Jon"),
                        "[2].lastName", is("Anderson"),
                        "[2].title", is("Manager"),
                        "[2].salary", is(85000),
                        "[2].hireDate", is("2007-03-18"));
    }

    @Test
    void marshalAnnotated() throws Exception {
        List<EmployeeAnnotated> employees = new ArrayList<>();
        EmployeeAnnotated one = new EmployeeAnnotated();
        one.setFirstName("Joe");
        one.setLastName("Smith");
        one.setTitle("Developer");
        one.setSalary(75000);
        one.setHireDate(formatter.parse("2009-11-01"));
        employees.add(one);

        EmployeeAnnotated two = new EmployeeAnnotated();
        two.setFirstName("Jane");
        two.setLastName("Doe");
        two.setTitle("Architect");
        two.setSalary(80000);
        two.setHireDate(formatter.parse("2008-01-15"));
        employees.add(two);

        EmployeeAnnotated three = new EmployeeAnnotated();
        three.setFirstName("Jon");
        three.setLastName("Anderson");
        three.setTitle("Manager");
        three.setSalary(85000);
        three.setHireDate(formatter.parse("2007-03-18"));
        employees.add(three);

        String expected = IOUtils.toString(BeanioTest.class.getResourceAsStream("/employees-csv.txt"),
                StandardCharsets.UTF_8);

        String result = RestAssured.given()
                .contentType(ContentType.JSON)
                .body(JsonbBuilder.create().toJson(employees))
                .post("/beanio/marshal/annotated")
                .then()
                .statusCode(200)
                .extract()
                .body()
                .asString();

        assertEquals(expected.trim(), result.trim());
    }

    @Test
    void unmarshalAnnotated() {
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/employees-csv.txt"))
                .post("/beanio/unmarshal/annotated")
                .then()
                .statusCode(200)
                .body(
                        "[0].firstName", is("Joe"),
                        "[0].lastName", is("Smith"),
                        "[0].title", is("Developer"),
                        "[0].salary", is(75000),
                        "[0].hireDate", is("2009-11-01"),
                        "[1].firstName", is("Jane"),
                        "[1].lastName", is("Doe"),
                        "[1].title", is("Architect"),
                        "[1].salary", is(80000),
                        "[1].hireDate", is("2008-01-15"),
                        "[2].firstName", is("Jon"),
                        "[2].lastName", is("Anderson"),
                        "[2].title", is("Manager"),
                        "[2].salary", is(85000),
                        "[2].hireDate", is("2007-03-18"));
    }

    @Test
    void marshalSingleObject() {
        Map<String, String> data = Map.of(
                "separator", ":",
                "value", "Content starts from here\nthen continues\nand ends here.",
                "key", "1234");

        String expected = data.get("key") + data.get("separator") + data.get("value");
        String result = RestAssured.given()
                .contentType(ContentType.JSON)
                .body(JsonbBuilder.create().toJson(data))
                .post("/beanio/marshal/single/object")
                .then()
                .statusCode(200)
                .extract()
                .body()
                .asString();

        assertEquals(expected, result.trim());
    }

    @Test
    void unmarshalSingleObject() {
        String data = "1234:Content starts from here\nthen continues\nand ends here.";
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(data)
                .post("/beanio/unmarshal/single/object")
                .then()
                .statusCode(200)
                .body(
                        "separator", is(":"),
                        "value", is(data.split(":")[1]),
                        "key", is("1234"));

    }

    @Test
    void unmarshalWithErrorHandler() {
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/employees-with-error.txt"))
                .post("/beanio/unmarshal/with/error/handler")
                .then()
                .statusCode(200)
                .body(
                        "[0].firstName", is("Joe"),
                        "[0].lastName", is("Smith"),
                        "[0].title", is("Developer"),
                        "[0].salary", is(75000),
                        "[0].hireDate", is("2009-11-01"),
                        "[1].firstName", is("Jane"),
                        "[1].lastName", is("Doe"),
                        "[1].title", is("Architect"),
                        "[1].salary", is(80000),
                        "[1].hireDate", is("2008-01-15"),
                        "[2].message", is("Invalid 'employee' record at line 3"),
                        "[2].record", is("employee"));
    }

    @Test
    void marshalComplexObject() throws Exception {
        String expected = IOUtils.toString(BeanioTest.class.getResourceAsStream("/complex-data.txt"), StandardCharsets.UTF_8);
        String result = RestAssured.given()
                .post("/beanio/marshal/complex/object")
                .then()
                .statusCode(200)
                .extract()
                .body()
                .asString();
        assertEquals(expected, result);
    }

    @Test
    void unmarshalComplexObject() {
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/complex-data.txt"))
                .post("/beanio/unmarshal/complex/object")
                .then()
                .statusCode(200)
                .body(
                        "[0].identifier", is("A1"),
                        "[0].recordType", is("PRICE"),
                        "[0].date", is("2008-08-03"),
                        "[1].identifier", is("B1"),
                        "[1].recordType", is("SECURITY"),
                        "[1].date", is("2008-08-03"),
                        "[2].value", is("HEADER END"),
                        "[3].price", is("12345.68"),
                        "[3].sedol", is("0001917"),
                        "[3].source", is("camel-beanio"),
                        "[4].price", is("59303290.02"),
                        "[4].sedol", is("0002374"),
                        "[4].source", is("camel-beanio"),
                        "[5].securityName", is("SECURITY ONE"),
                        "[5].sedol", is("0015219"),
                        "[5].source", is("camel-beanio"),
                        "[6].value", is("END OF SECTION 1"),
                        "[7].price", is("0.00"),
                        "[7].sedol", is("0076647"),
                        "[7].source", is("camel-beanio"),
                        "[8].price", is("999999999999.00"),
                        "[8].sedol", is("0135515"),
                        "[8].source", is("camel-beanio"),
                        "[9].securityName", is("SECURITY TWO"),
                        "[9].sedol", is("2000815"),
                        "[9].source", is("camel-beanio"),
                        "[10].securityName", is("SECURITY THR"),
                        "[10].sedol", is("2207122"),
                        "[10].source", is("camel-beanio"),
                        "[11].numberOfRecords", is(7));
    }

    @Test
    void splitter() {
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/employees-csv.txt"))
                .post("/beanio/split")
                .then()
                .statusCode(200)
                .body(
                        "[0].firstName", is("Joe"),
                        "[0].lastName", is("Smith"),
                        "[0].title", is("Developer"),
                        "[0].salary", is(75000),
                        "[0].hireDate", is("2009-11-01"),
                        "[1].firstName", is("Jane"),
                        "[1].lastName", is("Doe"),
                        "[1].title", is("Architect"),
                        "[1].salary", is(80000),
                        "[1].hireDate", is("2008-01-15"),
                        "[2].firstName", is("Jon"),
                        "[2].lastName", is("Anderson"),
                        "[2].title", is("Manager"),
                        "[2].salary", is(85000),
                        "[2].hireDate", is("2007-03-18"));
    }

    @Test
    void marshalEmployees() {
        ZonedDateTime expectedTime = ZonedDateTime.parse("2009-09-30T22:00:00Z[UTC]");
        RestAssured.given()
                .contentType(ContentType.TEXT)
                .body(BeanioTest.class.getResourceAsStream("/employees-csv.txt"))
                .post("/beanio/unmarshal/global")
                .then()
                .statusCode(200)
                .body(
                        "[0].firstName", is("Joe"),
                        "[0].lastName", is("Smith"),
                        "[0].title", is("Developer"),
                        "[0].salary", is(75000),
                        "[0].hireDate", is("2009-11-01"),
                        "[1].firstName", is("Jane"),
                        "[1].lastName", is("Doe"),
                        "[1].title", is("Architect"),
                        "[1].salary", is(80000),
                        "[1].hireDate", is("2008-01-15"),
                        "[2].firstName", is("Jon"),
                        "[2].lastName", is("Anderson"),
                        "[2].title", is("Manager"),
                        "[2].salary", is(85000),
                        "[2].hireDate", is("2007-03-18"));
    }
}
