package com.psg.dlsysinfo.dl_sysinfo_server.service;

import com.psg.dlsysinfo.dl_sysinfo_server.dto.RawCveMatch;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CachedInstalledSoftware;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResult;
import com.psg.dlsysinfo.dl_sysinfo_server.entity.CveMatchResultImpl;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.CveRepository;
import com.psg.dlsysinfo.dl_sysinfo_server.repository.InstalledSoftwareRepository;
import lombok.RequiredArgsConstructor;
import org.apache.maven.artifact.versioning.ComparableVersion;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
public class VulnerabilityScannerService {

    private final InstalledSoftwareRepository installedSoftwareRepository;
    private final CveRepository cveRepository;
    private final boolean LENIENT_VERSION_MATCHING = true;


    private final boolean DEBUG = true;

    public List<CveMatchResult> getVulnerabilitiesForDevice(Long deviceId) {
        List<Object[]> rawRows = cveRepository.findUnfilteredMatches(deviceId);

        if (DEBUG) {
            System.out.println("✅ Raw match count for device " + deviceId + ": " + rawRows.size());
        }

        return rawRows.stream()
                .map(this::mapToRawCveMatch)
                .peek(match -> {
                    System.out.printf(
                            "[Device %d] 🧪 CVE %-12s | Installed: %-10s | Start: %-10s | End: %-10s | Version: %-10s\n",
                            deviceId,
                            match.cveId(),
                            match.appVersion(),
                            match.versionStart(),
                            match.versionEnd(),
                            match.version()
                    );
                })
                .filter(match -> {
                    boolean result = isVulnerable(match);
                    System.out.println(result ? "✅ Marked vulnerable!" : "❌ Skipped (not vulnerable).");
                    return result;
                })
                .map(this::toCveMatchResult)
                .toList();
    }

    private RawCveMatch mapToRawCveMatch(Object[] row) {
        String cveId = (String) row[0];          // v.id
        String description = (String) row[1];    // v.description
        String severity = (String) row[2];       // ✅ v.severity
        String versionStart = (String) row[3];   // c.version_start
        String versionEnd = (String) row[4];     // c.version_end
        String version = (String) row[5];        // c.version
        String appVersion = (String) row[6];     // i.appVersion
        String appName = (String) row[7];        // i.appName
        Double score = row[9] != null ? ((Number) row[9]).doubleValue() : null; // v.cvss_score
        java.time.LocalDateTime lastModified = row[10] != null
                ? ((java.sql.Timestamp) row[10]).toLocalDateTime()
                : null;

        boolean lenient = LENIENT_VERSION_MATCHING
                          && versionStart == null && versionEnd == null
                          && version != null && !version.equals("*") && !version.equals("-");

        return new RawCveMatch(
                appName,           // ✅ appName (index 7)
                appVersion,        // ✅ appVersion (index 6)
                versionStart,      // ✅ versionStart (index 3)
                versionEnd,        // ✅ versionEnd (index 4)
                version,           // ✅ version (index 5)
                cveId,             // ✅ cveId (index 0)
                description,       // ✅ description (index 1)
                severity,          // ✅ severity (index 2)
                score,             // ✅ score (index 9)
                lastModified,      // ✅ lastModifiedDate (index 10)
                lenient            // ✅ lenient match flag
        );
    }



    private boolean isVulnerable(RawCveMatch match) {
        if (match.appVersion() == null || match.appVersion().isBlank()) return false;

        ComparableVersion installed = new ComparableVersion(match.appVersion().trim());

        boolean inRange = false;
        if (match.versionStart() != null || match.versionEnd() != null) {
            boolean startOK = match.versionStart() == null || installed.compareTo(new ComparableVersion(match.versionStart().trim())) >= 0;
            boolean endOK = match.versionEnd() == null || installed.compareTo(new ComparableVersion(match.versionEnd().trim())) <= 0;
            inRange = startOK && endOK;
        }

        boolean exactOrOlder = false;
        if (match.version() != null && !match.version().equals("*") && !match.version().equals("-")) {
            ComparableVersion vuln = new ComparableVersion(match.version().trim());
            if (LENIENT_VERSION_MATCHING) {
                exactOrOlder = installed.compareTo(vuln) <= 0; // vulnerable if installed version <= vuln version
            } else {
                exactOrOlder = installed.compareTo(vuln) == 0; // only match exact
            }
        }

        return inRange || exactOrOlder;
    }



    private boolean isVersionInRange(ComparableVersion installed, String start, String end) {
        boolean startOK = (start == null || installed.compareTo(new ComparableVersion(start)) >= 0);
        boolean endOK = (end == null || installed.compareTo(new ComparableVersion(end)) <= 0);
        return startOK && endOK;
    }

    private CveMatchResult toCveMatchResult(RawCveMatch raw) {
        return new CveMatchResultImpl(
                raw.cveId(),
                raw.severity(),
                raw.cvssScore(),
                raw.description(),
                raw.appName(),
                raw.appVersion(),
                raw.lastModifiedDate(),
                raw.lenient() // ✅ correctly passed now
        );
    }




    public Page<CveMatchResult> getPaginatedVulnerabilitiesForDevice(Long deviceId, Pageable pageable) {
        // fallback for paginated SQL-based view
        return cveRepository.findPaginatedVulnerabilitiesForDevice(deviceId, pageable);
    }

    private List<CveMatchResult> getVulnerabilitiesForSoftware(Long deviceId, String softwareName, String appVersion) {
        List<Object[]> rawRows = cveRepository.findUnfilteredMatches(deviceId);

        return rawRows.stream()
                .map(this::mapToRawCveMatch)
                .filter(match -> {
                    boolean nameMatch = match.appName().equalsIgnoreCase(softwareName);
                    boolean versionMatch = match.appVersion().equalsIgnoreCase(appVersion);
                    boolean vulnerable = isVulnerable(match);
                    return nameMatch && versionMatch && vulnerable;
                })
                .map(this::toCveMatchResult)
                .toList();
    }


    public void enrichCachedSoftwareEntries(List<CachedInstalledSoftware> entries) {
        for (CachedInstalledSoftware software : entries) {
            List<CveMatchResult> vulns = getVulnerabilitiesForSoftware(software.getDeviceId(), software.getSoftwareName(), software.getAppVersion());

            software.setTotalCves(vulns.size());
            software.setCveList(
                    vulns.stream()
                            .map(CveMatchResult::getCveId)
                            .collect(Collectors.joining(","))
            );
        }
}
}
