import java.util.Scanner;
import org.junit.Test;
import static org.junit.Assert.*;
import java.io.IOException;
import java.util.List;

/**
 * JUnit tests to ensure functionality of readData()
 */
public class BackendTests {

    @Test
    public void backendTest1() {
        // create new tree and backend
        Tree_Placeholder tree = new Tree_Placeholder();
        Backend backend = new Backend(tree);

        tree.insert(new Song("A L I E N S", "Coldplay","permanent wave",2017,148,88,43,-5,21));
        tree.insert(new Song("BO$$", "Fifth Harmony","dance pop",2015,103,87,81,-5,5));
        tree.insert(new Song("Cake By The Ocean", "DNCE","dance pop",2016,119,75,77,-5,4));

        try {
             backend.readData("songs.csv"); // try a nd read the data
             assertTrue("readData should complete without throwing exceptions", true); 
            
             // ensure getAndSetRange() returns a non empty list.
            List<String> allSongs = backend.getAndSetRange(null, null);
            assertNotNull("Backend should return a non-null list after reading data", allSongs);

        } catch (IOException e) {
            fail("readData should not throw IOException for 'songs.csv'. Error is: " + e.getMessage());
        } catch (Exception e) {
            fail("readData should not throw any exceptions. Unexpected error: " + e.getMessage());
        }
    
    }

    @Test
    public void backendTest2() {
        // create new tree and backend
        Tree_Placeholder tree2 = new Tree_Placeholder();
        Backend backend2 = new Backend(tree2);
        // "pretend to add songs to the tree... whatever that means"
        tree2.insert(new Song("A L I E N S", "Coldplay","permanent wave",2017,148,88,43,-5,21));
        tree2.insert(new Song("BO$$", "Fifth Harmony","dance pop",2015,103,87,81,-5,5));
        tree2.insert(new Song("Cake By The Ocean", "DNCE","dance pop",2016,119,75,77,-5,4));
        
        try {

            // -----TEST getAndSetRange()-------

            // test getAndSetRange()
            List<String> rangeResult1 = backend2.getAndSetRange(2015, 2017);
            assertNotNull("getAndSetRange should not return null", rangeResult1);
             
            // test no lower bound.
            List<String> rangeResult2 = backend2.getAndSetRange(null, 2016);
            assertNotNull("Unbounded lower range should not return null", rangeResult2);
            
            // test no upper bound.
            List<String> rangeResult3 = backend2.getAndSetRange(2016, null);
            assertNotNull("Unbounded upper range should not return null", rangeResult3);

            // test no bound at all
            List<String> rangeResult4 = backend2.getAndSetRange(null, null);
            assertNotNull("Completely unbounded range should not return null", rangeResult4);
            

            // -----TEST applyAndSetFilter()-------

            // Apply a loudness filter
            List<String> filterResult1 = backend2.applyAndSetFilter(-4);
            assertNotNull("applyAndSetFilter should not return null", filterResult1);

            // try the filter with null
            List<String> filterResult3 = backend2.applyAndSetFilter(null);
            assertNotNull("Null threshold should not return null", filterResult3);

        } catch (Exception e) {
            fail("Placeholder tree test should not throw exceptions. Error: " + e.getMessage());
        }

    }

    @Test
    public void backendTest3() {

        Tree_Placeholder tree3 = new Tree_Placeholder();
        Backend backend3 = new Backend(tree3);
        tree3.insert(new Song("A L I E N S", "Coldplay","permanent wave",2017,148,88,43,-5,21));
        tree3.insert(new Song("BO$$", "Fifth Harmony","dance pop",2015,103,87,81,-5,5));
        tree3.insert(new Song("Cake By The Ocean", "DNCE","dance pop",2016,119,75,77,-5,4));

        try {

            // Set range to include only 2015 and 2016 songs (2 hardcoded songs should appear)
            backend3.getAndSetRange(2015, 2016);

            // Insert one additional song that falls within this range
            Song extraSong = new Song("Extra Song", "Test Artist", "pop", 2016, 120, 80, 85, -5, 10);
            tree3.insert(extraSong);

            // get fiveMost()
            List<String> result = backend3.fiveMost();
            
            // ensure the proper list is returned.
            assertNotNull("Should return list of songs", result);
            assertEquals("Should return exactly 3 songs in range", 3, result.size());

            // Verify specific songs are in the result
            assertTrue("Should contain 'BO$$' from 2015", result.contains("BO$$"));

       } catch (Exception e) {
            fail("Three songs test should not throw exceptions. Error: " + e.getMessage());
       }
    }

    // --------INTEGRATION TESTS ------------

    /**
     * Integration test for loading data through frontend and then checking backend state.
     */
    @Test
    public void testLoadCommandIntegration() {
       
        // create objects to be used in tests.
        RBTreeIterable<Song> tree = new RBTreeIterable<>();
        Backend backend = new Backend(tree);
        String simulatedInput = "load songs.csv\nquit\n";
        Scanner scanner = new Scanner(simulatedInput);
        Frontend frontend = new Frontend(scanner, backend);
        
        try {
            // Run the frontend command loop
            frontend.runCommandLoop();
            
            // check backend has data by checking if range query returns results
            List<String> songs = backend.getAndSetRange(null, null);
            assertNotNull("Backend should return song list after loading", songs);
            assertFalse("Backend should have songs list after loading data", songs.isEmpty());
            
        } catch (Exception e) {
            fail("Integration test should not throw a exce[ption: " + e.getMessage());
        }
    }

    /**
     * Integration test for year range filtering through frontend commands.
     */
    @Test
    public void testYearFilterIntegration() {

        // Set up objects to be used in tests
        RBTreeIterable<Song> tree = new RBTreeIterable<>();
        Backend backend = new Backend(tree);
        
        // Add test songs with different years
        tree.insert(new Song("Song2015", "Artist1", "rock", 2015, 100, 80, 75, -5, 10));
        tree.insert(new Song("Song2016", "Artist2", "rock", 2016, 110, 85, 80, -4, 8));
        tree.insert(new Song("Song2020", "Artist3", "rock", 2020, 120, 90, 85, -3, 6));
        
        // Test that backend filtering works 
        List<String> directFilter = backend.getAndSetRange(2015, 2016);
        assertEquals("Backend should filter to 2 songs for 2015-2016 range", 2, directFilter.size());
        assertTrue("Should contain Song2015", directFilter.contains("Song2015"));
        assertTrue("Should contain Song2016", directFilter.contains("Song2016"));
        assertFalse("Should not contain Song2017", directFilter.contains("Song2020"));
        
        // Test frontend command processing 
        String simulatedInput = "year 2015 to 2016\nquit\n";
        Scanner scanner = new Scanner(simulatedInput);
        Frontend frontend = new Frontend(scanner, backend);
        
        try {
            // This checks the frontend can process commands
            frontend.runCommandLoop();
            assertTrue("Frontend should process without exceptions", true);
        } catch (Exception e) {
            fail("Frontend should process year command withoutt exceptions: " + e.getMessage());
        }
    }

    /**
     * Integration test for loudness filtering through frontend commands.
     */
    @Test
    public void testLoudnessFilterIntegration() {

        // Set up objects for tests 
        RBTreeIterable<Song> tree = new RBTreeIterable<>();
        Backend backend = new Backend(tree);
        
        // Add test songs with different loudness values
        tree.insert(new Song("quiet", "Artist1", "pop", 2020, 100, 80, 75, -10, 10)); 
        tree.insert(new Song("loud", "Artist2", "rock", 2020, 110, 85, 80, -2, 8));    
        tree.insert(new Song("medium", "Artist3", "jazz", 2020, 120, 90, 85, -5, 6));  
        
        // Test frontend processing of loudness command
        String simulatedInput = "loudness -4\nquit\n";
        Scanner scanner = new Scanner(simulatedInput);
        Frontend frontend = new Frontend(scanner, backend);
        
        try {
            frontend.runCommandLoop();
            
            // check backend filtering
            List<String> filteredSongs = backend.getAndSetRange(null, null);
            assertEquals("Should only have songs with loudness =< -4", 2, filteredSongs.size());
            assertTrue("Should contain quiet", filteredSongs.contains("quiet"));
            assertTrue("Should contain medium", filteredSongs.contains("medium"));
            assertFalse("Should not contain loud", filteredSongs.contains("loud"));
            
        } catch (Exception e) {
            fail("Loudness filter integration test failed: " + e.getMessage());
        }
    }

    /**
     * Integration test for displaying most danceable songs through frontend commands.
     */
    @Test
    public void testMostDanceableIntegration() {

        // Set up objects for tests 
        RBTreeIterable<Song> tree = new RBTreeIterable<>();
        Backend backend = new Backend(tree);
        
        // Add test songs with different danceability values
        tree.insert(new Song("LowDance", "Artist1", "rock", 2020, 100, 80, 50, -5, 10));  
        tree.insert(new Song("HighDance", "Artist2", "rock", 2020, 110, 85, 95, -4, 8)); 
        tree.insert(new Song("MidDance", "Artist3", "rock", 2020, 120, 90, 75, -3, 6));  
        
        // Test frontend processing of "show most danceable" command
        String simulatedInput = "show most danceable\nquit\n";
        Scanner scanner = new Scanner(simulatedInput);
        Frontend frontend = new Frontend(scanner, backend);
        
        try {
            frontend.runCommandLoop();
            
            // check backend's fiveMost returns correct order
            List<String> mostDanceable = backend.fiveMost();
            assertNotNull("Backend should return most danceable songs", mostDanceable);
            assertEquals("Should return 3 danceable songs", 3, mostDanceable.size());
            assertEquals("HighDance should be first", "HighDance", mostDanceable.get(0));
            assertEquals("LowDance should be third", "LowDance", mostDanceable.get(2));
            
        } catch (Exception e) {
            fail("Most danceable test failed: " + e.getMessage());
        }
    }


}
