package com.mvrent.carrental.config;

import com.mvrent.carrental.model.*;
import com.mvrent.carrental.repository.CarRepository;
import com.mvrent.carrental.repository.DriverRepository;
import com.mvrent.carrental.repository.MaintenanceRepository;
import com.mvrent.carrental.repository.ReviewRepository;
import com.mvrent.carrental.repository.UserRepository;
import com.mvrent.carrental.repository.VehicleLocationRepository;
import com.mvrent.carrental.service.ConfigService;
import com.mvrent.carrental.service.VendorService;

import java.math.BigDecimal;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.EnumSet;

@Component
@RequiredArgsConstructor
@Slf4j
public class DataInitializer implements CommandLineRunner {

    private final UserRepository userRepository;
    private final DriverRepository driverRepository;
    private final CarRepository carRepository;
    private final PasswordEncoder passwordEncoder;
    private final ConfigService configService;
    private final VendorService vendorService;
    private final JdbcTemplate jdbcTemplate;
    private final MaintenanceRepository maintenanceRepository;
    private final ReviewRepository reviewRepository;
    private final VehicleLocationRepository vehicleLocationRepository;

    @Override
    public void run(String... args) {
        // Fill new fleet columns on rows created before Phase 1 (null-safe, SQL level).
        backfillCarDefaults();

        // Organization + system config singletons (created on first access)
        configService.getOrganization();
        configService.getSystemConfig();
        seedCategories();

        seedAdmin();
        seed("demo", "demo@mvrent.local", "demo1234", "Demo Customer", Role.CUSTOMER);
        seedDrivers();
        seedVendor();
        seedDemoCustomerCategory();
        seedCarFeatures();
        seedTelematics();
        seedMaintenance();
        seedReviews();
    }

    /** Demo GPS positions across Kerala so the Live Tracking map isn't empty. */
    private void seedTelematics() {
        if (vehicleLocationRepository.count() > 0) return;
        double[][] coords = {
                {9.9312, 76.2673},   // Kochi
                {8.5241, 76.9366},   // Trivandrum
                {11.2588, 75.7804},  // Kozhikode
                {10.5276, 76.2144},  // Thrissur
                {9.5916, 76.5222}    // Kottayam
        };
        java.util.List<Car> cars = carRepository.findAll();
        int i = 0;
        for (Car c : cars) {
            if (i >= coords.length) break;
            vehicleLocationRepository.save(VehicleLocation.builder()
                    .car(c).latitude(coords[i][0]).longitude(coords[i][1])
                    .speedKph(i % 2 == 0 ? 0.0 : 42.0).heading(90.0 * i)
                    .odometer(38000 + i * 5200).ignitionOn(i % 2 != 0)
                    .updatedAt(java.time.LocalDateTime.now().minusMinutes(i))
                    .build());
            i++;
        }
        if (i > 0) log.info("Seeded {} demo vehicle locations", i);
    }

    private void seedMaintenance() {
        if (maintenanceRepository.count() > 0) return;
        carRepository.findAll().stream().findFirst().ifPresent(c ->
                maintenanceRepository.save(MaintenanceRecord.builder()
                        .car(c).type(MaintenanceType.SERVICE).status(MaintenanceStatus.COMPLETED)
                        .serviceDate(java.time.LocalDate.now().minusMonths(2)).odometer(36500)
                        .cost(new BigDecimal("4800")).garage("AutoCare Kochi")
                        .notes("Engine oil, filter, brake pads")
                        .nextServiceDate(java.time.LocalDate.now().plusMonths(4)).nextServiceOdometer(46500)
                        .build()));
        log.info("Seeded a demo maintenance record");
    }

    private void seedReviews() {
        if (reviewRepository.count() > 0) return;
        User demo = userRepository.findByUsername("demo").orElse(null);
        if (demo == null) return;
        carRepository.findAll().stream().findFirst().ifPresent(c -> {
            reviewRepository.save(Review.builder().car(c).user(demo).rating(5)
                    .comment("Spotless car and a smooth booking experience. Highly recommend!")
                    .approved(true).build());
            reviewRepository.save(Review.builder().car(c).user(demo).rating(4)
                    .comment("Great value for money. Pickup was quick.")
                    .approved(true).build());
        });
        log.info("Seeded demo reviews");
    }

    private void seedCarFeatures() {
        java.util.List<Car> cars = carRepository.findAll();
        boolean anyTags = cars.stream().anyMatch(c -> !c.getFeatureTags().isEmpty());
        if (anyTags) return;
        int i = 0;
        for (Car c : cars) {
            java.util.Set<String> tags = new java.util.LinkedHashSet<>();
            tags.add("AC");
            if (i % 2 == 0) { tags.add("GPS"); tags.add("Bluetooth"); }
            if (c.getSeats() >= 7) tags.add("Spacious");
            if (c.getFuelType() == FuelType.ELECTRIC) tags.add("Fast Charging");
            c.setFeatureTags(tags);
            carRepository.save(c);
            i++;
        }
        log.info("Seeded feature tags on {} cars", cars.size());
    }

    private void seedDemoCustomerCategory() {
        userRepository.findByUsername("demo").ifPresent(demo -> {
            if (demo.getCustomerCategory() == null) {
                configService.categoriesOfType(CategoryType.CUSTOMER).stream()
                        .filter(c -> c.getName().equals("Individual")).findFirst()
                        .ifPresent(cat -> {
                            demo.setCustomerCategory(cat);
                            userRepository.save(demo);
                        });
            }
        });
    }

    private void seedVendor() {
        // Idempotent: get-or-create the demo vendor, then ensure portal user + attachments.
        Vendor vendor = vendorService.findAll().stream()
                .filter(v -> v.getName().equals("Kerala Cabs")).findFirst()
                .orElseGet(() -> vendorService.save(Vendor.builder()
                        .name("Kerala Cabs").contactPerson("Joby Thomas")
                        .phone("+91 9847000000").email("ops@keralacabs.local")
                        .gstNumber("32ABCDE1234F1Z5").settlementDetails("A/C 00112233, IFSC SBIN0001234")
                        .commissionPercent(new BigDecimal("12")).active(true).build()));

        if (!userRepository.existsByUsername("vendor1")) {
            try {
                vendorService.createPortalUser(vendor.getId(), "vendor1", "vendor1@mvrent.local", "vendor123");
                log.info("Seeded vendor 'Kerala Cabs' + portal login vendor1/vendor123");
            } catch (IllegalArgumentException ignored) {
                // portal user already exists
            }
        }

        // Attach one car + one driver to the vendor for demo (only if none yet attached)
        if (carRepository.countByVendorId(vendor.getId()) == 0) {
            carRepository.findAll().stream().findFirst().ifPresent(c -> {
                c.setOwnership(CarOwnership.VENDOR);
                c.setVendor(vendor);
                carRepository.save(c);
            });
        }
        driverRepository.findByVendorIdOrderByName(vendor.getId()).stream().findAny()
                .ifPresentOrElse(d -> { }, () ->
                        driverRepository.findAll().stream()
                                .filter(d -> d.getName().contains("Suresh")).findFirst().ifPresent(d -> {
                                    d.setEmploymentType(DriverEmploymentType.VENDOR);
                                    d.setVendor(vendor);
                                    driverRepository.save(d);
                                }));
    }

    private void seedDrivers() {
        if (driverRepository.count() > 0) return;
        Category standard = configService.categoriesOfType(CategoryType.DRIVER).stream()
                .filter(c -> c.getName().equals("Standard")).findFirst().orElse(null);
        Category premium = configService.categoriesOfType(CategoryType.DRIVER).stream()
                .filter(c -> c.getName().equals("Premium")).findFirst().orElse(null);

        driverRepository.save(Driver.builder()
                .name("Rajesh Kumar").phone("+91 9847012345").licenceNumber("KL07-2018-0012345")
                .licenceExpiry(java.time.LocalDate.now().plusYears(2)).yearsExperience(6)
                .category(standard).employmentType(DriverEmploymentType.EMPLOYED)
                .dailyCharge(new java.math.BigDecimal("800")).status(DriverStatus.AVAILABLE).build());

        driverRepository.save(Driver.builder()
                .name("Suresh Nair").phone("+91 9847098765").licenceNumber("KL01-2015-0098765")
                .licenceExpiry(java.time.LocalDate.now().plusYears(1)).yearsExperience(11)
                .category(premium).employmentType(DriverEmploymentType.EMPLOYED)
                .dailyCharge(new java.math.BigDecimal("1200")).status(DriverStatus.AVAILABLE).build());

        log.info("Seeded 2 demo drivers");
    }

    private void seedAdmin() {
        User admin = userRepository.findByUsername("admin").orElse(null);
        if (admin == null) {
            admin = User.builder()
                    .username("admin")
                    .email("admin@mvrent.local")
                    .password(passwordEncoder.encode("admin123"))
                    .fullName("MV Rent Admin")
                    .role(Role.ADMIN)
                    .permissions(EnumSet.allOf(Permission.class))
                    .enabled(true)
                    .build();
            userRepository.save(admin);
            log.info("Seeded ADMIN user 'admin' (password: admin123) with all permissions");
        } else if (admin.getRole() == Role.ADMIN && admin.getPermissions().isEmpty()) {
            // Backfill permissions for an admin created before the permission model existed.
            admin.setPermissions(EnumSet.allOf(Permission.class));
            userRepository.save(admin);
            log.info("Backfilled all permissions onto existing ADMIN user 'admin'");
        }
    }

    private void seed(String username, String email, String rawPassword, String fullName, Role role) {
        if (userRepository.existsByUsername(username)) return;
        User user = User.builder()
                .username(username)
                .email(email)
                .password(passwordEncoder.encode(rawPassword))
                .fullName(fullName)
                .role(role)
                .enabled(true)
                .build();
        userRepository.save(user);
        log.info("Seeded {} user '{}' (password: {})", role, username, rawPassword);
    }

    private void seedCategories() {
        if (!configService.allCategories().isEmpty()) return;
        // Vehicle categories
        for (String n : new String[]{"Hatchback", "Sedan", "SUV", "MUV", "Luxury", "Bike"}) {
            safeAdd(CategoryType.VEHICLE, n);
        }
        // Driver categories
        for (String n : new String[]{"Standard", "Premium", "Chauffeur"}) {
            safeAdd(CategoryType.DRIVER, n);
        }
        // Customer categories
        for (String n : new String[]{"Individual", "Company", "Corporate"}) {
            safeAdd(CategoryType.CUSTOMER, n);
        }
        log.info("Seeded default vehicle/driver/customer categories");
    }

    private void backfillCarDefaults() {
        try {
            // Drop the stale enum CHECK constraint Hibernate created when Role was
            // only {ADMIN, CUSTOMER}; ddl-auto=update never widens it for STAFF/VENDOR.
            jdbcTemplate.execute("ALTER TABLE users DROP CONSTRAINT IF EXISTS users_role_check");

            jdbcTemplate.update("UPDATE cars SET status = 'ACTIVE' WHERE status IS NULL");
            jdbcTemplate.update("UPDATE cars SET ownership = 'OWNED' WHERE ownership IS NULL");
            jdbcTemplate.update("UPDATE cars SET units_total = 1 WHERE units_total IS NULL");

            // Phase 4: fill new booking columns on pre-existing rows.
            jdbcTemplate.update("UPDATE bookings SET with_driver = false WHERE with_driver IS NULL");
            jdbcTemplate.update("UPDATE bookings SET rental_unit = 'DAY' WHERE rental_unit IS NULL");
            jdbcTemplate.update("UPDATE bookings SET quantity = 1 WHERE quantity IS NULL");
        } catch (DataAccessException ex) {
            log.warn("Schema backfill skipped: {}", ex.getMessage());
        }
    }

    private void safeAdd(CategoryType type, String name) {
        try {
            configService.addCategory(type, name, null);
        } catch (IllegalArgumentException ignored) {
            // already present
        }
    }
}
