/*
 *  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.causeway.core.metamodel.services.metamodel;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import org.apache.causeway.applib.Identifier;
import org.apache.causeway.applib.services.inject.ServiceInjector;
import org.apache.causeway.applib.services.metamodel.DomainMember;
import org.apache.causeway.applib.services.metamodel.DomainModel;
import org.apache.causeway.commons.collections.Can;
import org.apache.causeway.core.metamodel.facets.FacetedMethod;
import org.apache.causeway.core.metamodel.id.TypeIdentifierTestFactory;
import org.apache.causeway.core.metamodel.spec.Hierarchical;
import org.apache.causeway.core.metamodel.spec.ObjectSpecification;
import org.apache.causeway.core.metamodel.spec.feature.ObjectAction;
import org.apache.causeway.core.metamodel.spec.impl._JUnitSupport;
import org.apache.causeway.core.mmtestsupport.MetaModelContext_forTesting;

// keep public for JABX
public class MetaModelServiceDefaultTest {

    ServiceInjector stubServicesInjector;
    MetaModelServiceDefault mockMetaModelService;
    ObjectAction action;
    ObjectSpecification mockSpec;

    FacetedMethod mockFacetedMethod;

    @BeforeEach
    @SuppressWarnings("unchecked")
    void setUp() throws Exception {

        mockFacetedMethod = Mockito.mock(FacetedMethod.class);
        Mockito.when(mockFacetedMethod.getMetaModelContext()).thenReturn(MetaModelContext_forTesting.buildDefault());
        Mockito.when(mockFacetedMethod.getFeatureIdentifier()).thenReturn(Identifier.actionIdentifier(
              TypeIdentifierTestFactory.newCustomer(), "reduceheadcount"));
        Mockito.when(mockFacetedMethod.getFacet(Mockito.any(Class.class))).thenReturn(null);
        Mockito.when(mockFacetedMethod.parameters()).thenReturn(Can.empty());

        mockSpec = Mockito.mock(ObjectSpecification.class);
        Mockito.when(mockSpec.getFullIdentifier()).thenReturn("mocked");
        Mockito.when(mockSpec.logicalTypeName()).thenReturn("logicalType");
        Mockito.when(mockSpec.subclasses(Hierarchical.Depth.DIRECT)).thenReturn(Can.empty());
        Mockito.when(mockSpec.isInjectable()).thenReturn(true);

        action = _JUnitSupport.actionForMethod(mockFacetedMethod);

        mockMetaModelService = Mockito.mock(MetaModelServiceDefault.class);
        Mockito.when(mockMetaModelService.getDomainModel())
            .thenReturn(new DomainModelDefault(List.of(new DomainMemberDefault(mockSpec, action))));

    }

    @AfterEach
    void tearDown() throws Exception {
    }

    @Test @DisplayName("member to XML marshalling should not throw")
    void member_marshalling() throws JAXBException {
        DomainMember domainMember = new DomainMemberDefault(mockSpec, action);

        JAXBContext jaxbContext = JAXBContext.newInstance(DomainMemberDefault.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        jaxbMarshaller.marshal(domainMember, noopOutput());
    }

    @Test @DisplayName("model to XML marshalling should not throw")
    void model_marshalling() throws JAXBException {
        DomainModel domainMembers = mockMetaModelService.getDomainModel();

        JAXBContext jaxbContext = JAXBContext.newInstance(DomainModelDefault.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        jaxbMarshaller.marshal(domainMembers, noopOutput());
    }

    @Test @DisplayName("example to XML marshalling should not throw")
    void example_marshalling() throws JAXBException {

        JAXBContext jaxbContext = JAXBContext.newInstance(Employees.class);
        Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
        jaxbMarshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);

        jaxbMarshaller.marshal(createEmployees(), noopOutput());
    }

    // -- HELPER

    private OutputStream noopOutput(){
        return new OutputStream() {
            @Override public void write(final int b) throws IOException {}
        };
    }

    @XmlRootElement(name = "employee")
    @XmlAccessorType (XmlAccessType.FIELD)
    public static class Employee
    {
        private Integer id;
        private String firstName;
        private String lastName;
        private double income;

        //Getters and Setters

        public Integer getId() {
            return id;
        }
        public void setId(final Integer id) {
            this.id = id;
        }
        public String getFirstName() {
            return firstName;
        }
        public void setFirstName(final String firstName) {
            this.firstName = firstName;
        }
        public String getLastName() {
            return lastName;
        }
        public void setLastName(final String lastName) {
            this.lastName = lastName;
        }
        public double getIncome() {
            return income;
        }
        public void setIncome(final double income) {
            this.income = income;
        }

    }

    @XmlRootElement(name = "employees")
    @XmlAccessorType (XmlAccessType.FIELD)
    public static class Employees
    {
        @XmlElement(name = "employee")
        private List<Employee> employees = null;

        public List<Employee> getEmployees() {
            return employees;
        }

        public void setEmployees(final List<Employee> employees) {
            this.employees = employees;
        }
    }

    private Employees createEmployees(){
        Employees employees = new Employees();
        employees.setEmployees(new ArrayList<Employee>());
        //Create two employees
        Employee emp1 = new Employee();
        emp1.setId(1);
        emp1.setFirstName("Lokesh");
        emp1.setLastName("Gupta");
        emp1.setIncome(100.0);

        Employee emp2 = new Employee();
        emp2.setId(2);
        emp2.setFirstName("John");
        emp2.setLastName("Mclane");
        emp2.setIncome(200.0);

        //Add the employees in list
        employees.getEmployees().add(emp1);
        employees.getEmployees().add(emp2);

        return employees;
    }

}
