package com.nisum.BookInfo.controller;

import com.nisum.BookInfo.dto.BookDto;
import com.nisum.BookInfo.entity.Book;
import com.nisum.BookInfo.mapper.BookMapper;
import com.nisum.BookInfo.service.BookService;
import com.nisum.BookInfo.validator.CommonServiceValidator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import static org.springframework.test.web.servlet.client.MockMvcWebTestClient.bindToController;

@SpringBootTest
class BookControllerTest {
    @Mock
    private BookService bookService;  // Mock the BookService

    @Mock
    private CommonServiceValidator validatorService;  // Mock ValidatorService for book validation

    @InjectMocks
    private BookController bookController;  // The controller under test

   /* private BookDto validBookDto;
    private Book savedBook;*/

    @BeforeEach
    public void setUp() {
        BookDto bookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book = new Book(1, "C++", "This is c++", "C publiser", "Test Author");


        //when(bookDao.saveBook(any(Book.class))).thenReturn(Mono.just(book));
        MockitoAnnotations.openMocks(this);

    }

    @Test
    public void createBook_shouldReturnSuccess_whenBookIsValid() {
        BookDto bookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book = new Book(1, "C++", "This is c++", "C publiser", "Test Author");

        // Mock the validateBook() and saveBook() methods
        when(validatorService.isBookIdValid(bookDto.getBookId())).thenReturn(Mono.just(true));  // Validation passes
        when(validatorService.isBookNameValid(bookDto.getName())).thenReturn(Mono.just(true));
        when(bookService.saveBook(bookDto)).thenReturn(Mono.just(book));  // Return saved book

        // Directly call the controller method (createBook) with the valid BookDto
        Mono<?> result = bookController.createBook(Mono.just(bookDto));

        // Use StepVerifier to check the result
        StepVerifier.create(result)
                .expectNextMatches(response -> {
                    // Assert that the response is a ResponseEntity with the expected message
                    assertTrue(response instanceof ResponseEntity);
                    ResponseEntity<?> responseEntity = (ResponseEntity<?>) response;
                    assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
                    return true;
                })
                .verifyComplete();  // Ensure the Mono completes without error
    }

    @Test
    public void createBook_shouldReturnError_whenValidationFails() {
        // Given: Create an invalid BookDto (for validation failure)
        BookDto invalidBookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        when(validatorService.isBookIdValid(invalidBookDto.getBookId())).thenReturn(Mono.just(true));  // Validation passes
        when(validatorService.isBookNameValid(invalidBookDto.getName())).thenReturn(Mono.just(true));
        // Simulate a validation failure (Mono.error)
        when(bookService.saveBook(invalidBookDto)).thenReturn(Mono.error(new IllegalArgumentException("Invalid publiser data")));

        // Directly call the controller method with invalid BookDto
        Mono<?> result = bookController.createBook(Mono.just(invalidBookDto));

        // Use StepVerifier to verify that the error is propagated
        StepVerifier.create(result)
                .expectError(IllegalArgumentException.class)  // Expect the error thrown by validation
                .verify();


    }

    //for update case
    @Test
    void updateBook_ShouldReturnUpdatedBook_WhenIdIsValid() {
        BookDto bookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book = new Book(1, "C++", "This is c++", "C publiser", "Test Author");

        // Mock the validateBook() and saveBook() methods
        when(validatorService.isBookIdValid(bookDto.getBookId())).thenReturn(Mono.just(true));  // Validation passes
        when(validatorService.isBookNameValid(bookDto.getName())).thenReturn(Mono.just(true));
        when(bookService.updateBook(any(Integer.class), any(BookDto.class))).thenReturn(Mono.just(book));

        // Make a test call to the controller method
        Mono<?> result = bookController.updateBook(1, Mono.just(bookDto));

        // StepVerifier to test the result
        StepVerifier.create(result)
                .expectNextMatches(response -> {
                    // Assert that the response is a ResponseEntity with the expected message
                    assertTrue(response instanceof ResponseEntity);
                    ResponseEntity<?> responseEntity = (ResponseEntity<?>) response;
                    assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
                    return true;
                })
                .verifyComplete();  //


    }
    //for getAllBook

    @Test
    void getAllBooks_ShouldReturnListOfBooks() {
        Book book1 = new Book(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book2 = new Book(2, "Java", "This is Java", "Java publiser", "Test Java Author");
        when(bookService.getAllBooks()).thenReturn(Flux.just(book1, book2));

        // Call the controller method
        Flux<Book> result = bookController.getAllBooks();

        // Verify the response using StepVerifier
        StepVerifier.create(result).
                expectNextMatches(book -> book.getBookId().equals(1))
                . expectNextMatches(book -> book.getBookId().equals(2))
                .verifyComplete();  //

    }
    //get single book

    @Test
    void getBookById_ShouldReturnBookWithStatusOk() {
        BookDto bookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book = new Book(1, "C++", "This is c++", "C publiser", "Test Author");

        when(validatorService.isBookIdValid(bookDto.getBookId())).thenReturn(Mono.just(true));  // Validation passes
        when(validatorService.isBookNameValid(bookDto.getName())).thenReturn(Mono.just(true));
        when(bookService.getBookById(1)).thenReturn(Mono.just(book));

        // Call the controller method
        Mono<?> result = bookController.getBookById(1);

        // Verify the response using StepVerifier
        StepVerifier.create(result)
                .expectNextMatches(response -> {
                    // Assert that the response is a ResponseEntity with the expected message
                    assertTrue(response instanceof ResponseEntity);
                    ResponseEntity<?> responseEntity = (ResponseEntity<?>) response;
                    assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
                    return true;
                })
                .verifyComplete();  //



    }

    //delete
    @Test
    void deleteBook_ShouldReturnOkWhenDeletedSuccessfully() {
        BookDto bookDto = new BookDto(1, "C++", "This is c++", "C publiser", "Test Author");
        Book book = new Book(1, "C++", "This is c++", "C publiser", "Test Author");

        when(validatorService.isBookIdValid(bookDto.getBookId())).thenReturn(Mono.just(true));  // Validation passes
        when(validatorService.isBookNameValid(bookDto.getName())).thenReturn(Mono.just(true));
        when(bookService.deleteBook(1)).thenReturn(Mono.empty());

        // Call the controller method
        Mono<?> result = bookController.deleteBook(1);

        // Verify the response using StepVerifier
        StepVerifier.create(result)
                .expectNextMatches(response -> {
                    // Assert that the response is a ResponseEntity with the expected message
                    assertTrue(response instanceof ResponseEntity);
                    ResponseEntity<?> responseEntity = (ResponseEntity<?>) response;
                    assertEquals(HttpStatus.OK, responseEntity.getStatusCode());
                    return true;
                })
                .verifyComplete();  //
    }





}