package com.psg.dlsysinfo.dl_sysinfo_server.service;

import com.psg.dlsysinfo.dl_sysinfo_server.dto.*;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.*;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.*;
import com.psg.dlsysinfo.dl_sysinfo_server.security.EncryptionService;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

@Service
public class DeviceService {

    @Autowired
    private DevicesRepository devicesRepository;

    @Autowired
    private EncryptionService encryptionService;

    @Autowired
    private IpAddressRepository ipAddressRepository;


    @Autowired
    private CveRepository cveRepository;

    @Autowired
    private InstalledSoftwareRepository installedSoftwareRepository;

    @Autowired
    private VulnerabilityScannerService vulnScanner;

    @Autowired
    private ClientRepository clientRepository;



    public List<DeviceDTO> getDevicesForClient(Long clientId) {
        System.out.println("🔍 Fetching devices for client ID: " + clientId);
        List<Devices> devices = devicesRepository.findByClient_ClientId(clientId);

        return devices.stream().map(device -> {
            DeviceDTO dto = new DeviceDTO();
            dto.setDeviceId(device.getDeviceId());
            dto.setLastCheckedIn(device.getLastCheckedIn());
            try {
                dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
            } catch (Exception e) {
                dto.setHostname("DECRYPTION_FAILED");
            }
            return dto;
        }).toList();
    }

    public Devices findOrCreateDevice(String hostname, String hashedHostname, Client client) {
        System.out.println("📥 findOrCreateDevice called with:");
        System.out.println("  🔹 hostname = " + hostname);
        System.out.println("  🔹 hashedHostname = " + hashedHostname);
        System.out.println("  🔹 clientId = " + client.getClientId());

        Devices device = devicesRepository.findByHashedHostname(hashedHostname).orElse(null);

        boolean isNew = false;
        if (device == null) {
            System.out.println("🆕 No existing device found. Creating new one...");
            device = new Devices();
            device.setHashedHostname(hashedHostname);
            isNew = true;
        } else {
            System.out.println("✅ Existing device found. Updating...");
        }

        device.setClient(client);
        device.setLastCheckedIn(LocalDateTime.now());

        try {
            device.setEncryptedHostname(encryptionService.encryptData(hostname));
        } catch (Exception e) {
            System.out.println("❌ Failed to encrypt hostname: " + e.getMessage());
            throw new RuntimeException("Failed to encrypt hostname", e);
        }

        Devices savedDevice = devicesRepository.save(device);

        if (isNew) {
            System.out.println("📥 New device saved with ID: " + savedDevice.getDeviceId());
        } else {
            System.out.println("🔄 Existing device updated with ID: " + savedDevice.getDeviceId());
        }

        return savedDevice;
    }

    public Devices updateDeviceCheckIn(Devices device, Client client, String hostname) {
        device.setClient(client);
        device.setLastCheckedIn(LocalDateTime.now());
        try {
            device.setEncryptedHostname(encryptionService.encryptData(hostname));
        } catch (Exception e) {
            throw new RuntimeException("Failed to encrypt hostname", e);
        }
        Devices updated = devicesRepository.save(device);
        System.out.println("🔄 Updated device ID: " + updated.getDeviceId());
        return updated;
    }

    public Devices saveDevice(Devices device) {
        System.out.println("💾 Saving device to DB: " + device.getDeviceId());
        return devicesRepository.save(device);
    }

    @Transactional
    public void populateDeviceFromDTO(Devices device, SystemInfoDTO dto) {
        System.out.println("🧩 Populating fields from DTO for device: " + device.getDeviceId());

        System.out.println("🔍 DTO OS Name: " + dto.getOsName());
        System.out.println("🔍 DTO GPU: " + dto.getGpuNames());
        System.out.println("📡 IP DTOs: " + dto.getIpAddresses());

        device.getDrives().clear();
        for (DriveInfoDTO driveDTO : dto.getDrives()) {
            Drive drive = new Drive();
            drive.setDevice(device);
            drive.setName(driveDTO.getName());
            drive.setTotalSizeGB(driveDTO.getTotalSizeGB());
            drive.setFreeSpaceGB(driveDTO.getFreeSpaceGB());
            drive.setDriveType(driveDTO.getDriveType());
            device.getDrives().add(drive);
        }

        ipAddressRepository.deleteByDevice(device);
        if (dto.getIpAddresses() != null) {
            for (IpAddressDTO ipDto : dto.getIpAddresses()) {
                if (ipDto.getInterfaceName() != null && ipDto.getInterfaceName().contains("Loopback Pseudo")) {
                    continue; // ✅ Skip loopback pseudo interfaces
                }

                IpAddress ipAddress = new IpAddress();
                ipAddress.setDevice(device);
                ipAddress.setIpAddress(ipDto.getIpAddress());
                ipAddress.setInterfaceName(ipDto.getInterfaceName());
                ipAddress.setMacAddress(ipDto.getMacAddress());
                ipAddressRepository.save(ipAddress);
            }
        }


        device.setOsName(dto.getOsName());
        device.setOsVersion(dto.getOsVersion());
        device.setWindowsVersion(dto.getWindowsVersion());
        device.setWindowsBuild(dto.getWindowsBuild());
        device.setOsArchitecture(dto.getOsArchitecture());
        device.setProcessorName(dto.getProcessorName());
        device.setProcessorArchitecture(dto.getProcessorArchitecture());
        device.setGpuName(String.join(", ", dto.getGpuNames()));
        device.setTotalMemory(dto.getTotalMemory());
        device.setLastBootTime(dto.getLastBootTime());
        device.setLastCheckedIn(LocalDateTime.now());
    }

    public DetailedDeviceDTO getDeviceDetails(Long deviceId) {
        Devices device = devicesRepository.findById(deviceId)
                .orElseThrow(() -> new RuntimeException("Device not found"));

        DetailedDeviceDTO dto = new DetailedDeviceDTO();
        try {
            dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
        } catch (Exception e) {
            dto.setHostname("DECRYPTION_FAILED");
        }

        dto.setDeviceId(device.getDeviceId());
        dto.setOsName(device.getOsName());
        dto.setOsVersion(device.getOsVersion());
        dto.setWindowsVersion(device.getWindowsVersion());
        dto.setWindowsBuild(device.getWindowsBuild());
        dto.setOsArchitecture(device.getOsArchitecture());
        dto.setProcessorName(device.getProcessorName());
        dto.setProcessorArchitecture(device.getProcessorArchitecture());
        if (device.getGpuName() != null) {
            dto.setGpuNames(
                    Arrays.stream(device.getGpuName().split(","))
                            .map(String::trim)
                            .collect(Collectors.toList())
            );
        } else {
            dto.setGpuNames(Collections.emptyList());
        }

        dto.setTotalMemory(device.getTotalMemory());
        dto.setLastBootTime(device.getLastBootTime());
        dto.setLastCheckedIn(device.getLastCheckedIn());

        dto.setDrives(device.getDrives().stream().map(d -> {
            DriveInfoDTO dDto = new DriveInfoDTO();
            dDto.setName(d.getName());
            dDto.setTotalSizeGB(d.getTotalSizeGB());
            dDto.setFreeSpaceGB(d.getFreeSpaceGB());
            dDto.setDriveType(d.getDriveType());
            return dDto;
        }).collect(Collectors.toList()));

        dto.setIpAddresses(
                ipAddressRepository.findByDeviceDeviceId(deviceId).stream()
                        .filter(ip -> ip.getInterfaceName() == null || !ip.getInterfaceName().contains("Loopback Pseudo"))
                        .map(ip -> {
                            IpAddressDTO ipDto = new IpAddressDTO();
                            ipDto.setIpAddress(ip.getIpAddress());
                            ipDto.setInterfaceName(ip.getInterfaceName());
                            ipDto.setMacAddress(ip.getMacAddress());
                            return ipDto;
                        })
                        .collect(Collectors.toList())
        );



        dto.setInstalledApplications(
                installedSoftwareRepository.findByDevice(device).stream()
                        .map(app -> {
                            InstalledAppDTO appDto = new InstalledAppDTO();
                            appDto.setApp_name(app.getAppName());
                            appDto.setApp_version(app.getAppVersion());
                            appDto.setPublisher(app.getPublisher());
                            return appDto;
                        })
                        .collect(Collectors.toList())
        );



        return dto;
    }

    public List<DeviceDTO> getAllDevices() {
        List<Devices> devices = devicesRepository.findAll();

        return devices.stream().map(device -> {
            DeviceDTO dto = new DeviceDTO();
            dto.setDeviceId(device.getDeviceId());
            dto.setLastCheckedIn(device.getLastCheckedIn());
            dto.setClientId(device.getClient().getClientId());
            dto.setClientIdentifier(device.getClient().getClientIdentifier());

            try {
                dto.setHostname(encryptionService.decryptData(device.getEncryptedHostname()));
            } catch (Exception e) {
                dto.setHostname("DECRYPTION_FAILED");
            }

            try {
                dto.setClientName(encryptionService.decryptData(device.getClient().getClientNameEncrypted()));
            } catch (Exception e) {
                dto.setClientName("[Decryption failed]");
            }

            return dto;
        }).toList();
    }


    public Map<Long, List<CveMatchResult>> getVulnerabilitiesGroupedByDevice(Long clientId) {
        List<Devices> devices = devicesRepository.findByClient_ClientId(clientId);

        return devices.stream()
                .collect(Collectors.toMap(
                        Devices::getDeviceId,
                        device -> vulnScanner.getVulnerabilitiesForDevice(device.getDeviceId())
                ));
    }
    @Transactional
    public void deleteDeviceAndChildren(Long deviceId) {
        Devices device = devicesRepository.findById(deviceId)
                .orElseThrow(() -> new RuntimeException("Device not found"));

        devicesRepository.delete(device); // 🚀 This cascades everything
    }
    @Transactional
    public void reassignClient(Long deviceId, Long newClientId) {
        Devices device = devicesRepository.findById(deviceId)
                .orElseThrow(() -> new RuntimeException("Device not found with ID: " + deviceId));

        Client newClient = clientRepository.findById(newClientId)
                .orElseThrow(() -> new RuntimeException("Client not found with ID: " + newClientId));

        device.setClient(newClient);
        devicesRepository.save(device);

        System.out.println("🔄 Reassigned device " + deviceId + " to client " + newClientId);
    }




}
