package com.mongo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.util.StopWatch;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

@SpringBootApplication
public class App implements CommandLineRunner {

    @Autowired
    private MongoTemplate mongoTemplate;

    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

    /**
     * @brief Application entry point
     * @param args Command Line Arguments
     */

    @Override
    public void run(String... args) {
        // Configuration
        int totalRecords = 2_000_000;
        int batchSize = 10_000;

        // Clean database before starting 
        mongoTemplate.dropCollection(Student.class);

        // Run Tests
        performWriteTest(totalRecords, batchSize);
        performReadTest();
    }

    /**
     * @brief Measure how much time it takes to write totalRecords
     * @param totalRecords the number of records should be above million
     * @param batchsize the size of batch that we read at a time
     */
    private void performWriteTest(int totalRecords, int batchSize) {
        System.out.println("\n STARTING WRITE BENCHMARK ");
        StopWatch watch = new StopWatch();
        watch.start();

        for (int i = 0; i < totalRecords / batchSize; i++) {
            List<Student> batch = new ArrayList<>();
            for (int j = 0; j < batchSize; j++) {
                
                batch.add(new Student("Student_" + (i * batchSize + j), "ID_" + (i * batchSize + j)));
            }
            //insert here is faster than save() because we don't worry about duplication here
            mongoTemplate.insert(batch, Student.class);
        }

        watch.stop();
        System.out.printf("WRITE COMPLETE: %d records in %.2f seconds (%d ops/sec)%n", 
                totalRecords, watch.getTotalTimeSeconds(), (int)(totalRecords / watch.getTotalTimeSeconds()));
    }

    /**
     * @brief Measure how much time it takes to read the entire database and one record (search) lookup
     */
    private void performReadTest() {
        System.out.println("\n STARTING SMART READ BENCHMARK ");

        
        // We pick a random ID from the middle of the set
        String searchId = "ID_543210"; 
        StopWatch pointWatch = new StopWatch();
        pointWatch.start();

        Query query = new Query(Criteria.where("studentId").is(searchId));
        Student found = mongoTemplate.findOne(query, Student.class);

        pointWatch.stop();
        System.out.println("1. POINT READ (Indexed Search):");
        System.out.println("   - Found: " + found);
        System.out.println("   - Time: " + pointWatch.getTotalTimeMillis() + " ms");

        //  Full Stream Read (Testing Retrieval Speed without crashing RAM)
        StopWatch streamWatch = new StopWatch();
        streamWatch.start();
        
        long count = 0;
        // Using Stream ensures we don't load 1M objects into RAM at once
        try (Stream<Student> studentStream = mongoTemplate.stream(new Query(), Student.class)) {
            count = studentStream.count();
        }

        streamWatch.stop();
        System.out.println("2. STREAM READ (Full Collection Scan):");
        System.out.println("   - Processed: " + count + " records");
        System.out.println("   - Time: " + streamWatch.getTotalTimeSeconds() + " seconds");
    }
}