-- This script defines PostgreSQL functions and triggers for managing service scores:
-- 1. Automatically calculates and updates privacy, trust, and overall scores 
--    for services when services or their attributes change.
-- 2. Updates the isRecentlyListed flag for services listed within the last 15 days.
-- 3. Queues asynchronous score recalculation in "ServiceScoreRecalculationJob" 
--    when an "Attribute" definition (e.g., points) is updated, ensuring 
--    efficient handling of widespread score updates.

-- Drop existing triggers first
DROP TRIGGER IF EXISTS service_score_update_trigger ON "Service";
DROP TRIGGER IF EXISTS service_attribute_change_trigger ON "ServiceAttribute";
DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute";

-- Drop existing functions
DROP FUNCTION IF EXISTS calculate_service_scores();
DROP FUNCTION IF EXISTS calculate_privacy_score();
DROP FUNCTION IF EXISTS calculate_trust_score();
DROP FUNCTION IF EXISTS calculate_overall_score();
DROP FUNCTION IF EXISTS recalculate_scores_for_attribute();

-- Calculate privacy score based on service attributes and properties
CREATE OR REPLACE FUNCTION calculate_privacy_score(service_id INT) 
RETURNS INT AS $$
DECLARE
    privacy_score INT := 50; -- Start from middle value (50)
    kyc_factor INT;
    onion_factor INT := 0;
    i2p_factor INT := 0;
    monero_factor INT := 0;
    open_source_factor INT := 0;
    p2p_factor INT := 0;
    decentralized_factor INT := 0;
    attributes_score INT := 0;
BEGIN
    -- Get service data
    SELECT 
        CASE 
            WHEN "kycLevel" = 0 THEN 25  -- No KYC is best for privacy
            WHEN "kycLevel" = 1 THEN 10   -- Minimal KYC
            WHEN "kycLevel" = 2 THEN -5  -- Moderate KYC
            WHEN "kycLevel" = 3 THEN -15 -- More KYC
            WHEN "kycLevel" = 4 THEN -25 -- Full mandatory KYC
            ELSE 0                     -- Default to no change
        END
    INTO kyc_factor
    FROM "Service" 
    WHERE "id" = service_id;
    
    -- Check for onion URLs
    IF EXISTS (
        SELECT 1 FROM "Service" 
        WHERE "id" = service_id AND array_length("onionUrls", 1) > 0
    ) THEN
        onion_factor := 5;
    END IF;
    
    -- Check for i2p URLs
    IF EXISTS (
        SELECT 1 FROM "Service" 
        WHERE "id" = service_id AND array_length("i2pUrls", 1) > 0
    ) THEN
        i2p_factor := 5;
    END IF;
    
    -- Check for Monero acceptance
    IF EXISTS (
        SELECT 1 FROM "Service" 
        WHERE "id" = service_id AND 'MONERO' = ANY("acceptedCurrencies")
    ) THEN
        monero_factor := 5;
    END IF;
    
    -- Calculate score from privacy attributes - directly use the points
    SELECT COALESCE(SUM(a."privacyPoints"), 0)
    INTO attributes_score
    FROM "ServiceAttribute" sa
    JOIN "Attribute" a ON sa."attributeId" = a."id"
    WHERE sa."serviceId" = service_id AND a."category" = 'PRIVACY';
    
    -- Calculate final privacy score (base 100)
    privacy_score := privacy_score + kyc_factor + onion_factor + i2p_factor + monero_factor + open_source_factor + p2p_factor + decentralized_factor + attributes_score;
    
    -- Ensure the score is in reasonable bounds (0-100)
    privacy_score := GREATEST(0, LEAST(100, privacy_score));
    
    RETURN privacy_score;
END;
$$ LANGUAGE plpgsql;

-- Calculate trust score based on service attributes and verification status
CREATE OR REPLACE FUNCTION calculate_trust_score(service_id INT) 
RETURNS INT AS $$
DECLARE
    trust_score INT := 50; -- Start from middle value (50)
    verification_factor INT;
    attributes_score INT := 0;
BEGIN
    -- Get verification status factor
    SELECT 
        CASE 
            WHEN "verificationStatus" = 'VERIFICATION_SUCCESS' THEN 10
            WHEN "verificationStatus" = 'APPROVED' THEN 5
            WHEN "verificationStatus" = 'COMMUNITY_CONTRIBUTED' THEN 0
            WHEN "verificationStatus" = 'VERIFICATION_FAILED' THEN -50
            ELSE 0
        END
    INTO verification_factor
    FROM "Service" 
    WHERE id = service_id;
    
    -- Calculate score from trust attributes - directly use the points
    SELECT COALESCE(SUM(a."trustPoints"), 0)
    INTO attributes_score
    FROM "ServiceAttribute" sa
    JOIN "Attribute" a ON sa."attributeId" = a.id
    WHERE sa."serviceId" = service_id AND a.category = 'TRUST';

    -- Apply penalty if service was listed within the last 15 days
    IF EXISTS (
        SELECT 1
        FROM "Service"
        WHERE id = service_id 
        AND "listedAt" IS NOT NULL 
        AND "verificationStatus" = 'APPROVED'
        AND (NOW() - "listedAt") <= INTERVAL '15 days'
    ) THEN
        trust_score := trust_score - 10;
        -- Update the isRecentlyListed flag to true
        UPDATE "Service"
        SET "isRecentlyListed" = TRUE
        WHERE id = service_id;
    ELSE
        -- Update the isRecentlyListed flag to false
        UPDATE "Service"
        SET "isRecentlyListed" = FALSE
        WHERE id = service_id;
    END IF;
    
    -- Calculate final trust score (base 100)
    trust_score := trust_score + verification_factor + attributes_score;
    
    -- Ensure the score is in reasonable bounds (0-100)
    trust_score := GREATEST(0, LEAST(100, trust_score));
    
    RETURN trust_score;
END;
$$ LANGUAGE plpgsql;

-- Calculate overall score based on weighted average of privacy and trust scores
CREATE OR REPLACE FUNCTION calculate_overall_score(service_id INT, privacy_score INT, trust_score INT) 
RETURNS INT AS $$
DECLARE
    overall_score INT;
BEGIN
    overall_score := CAST(ROUND(((privacy_score * 0.6) + (trust_score * 0.4)) / 10.0) AS INT);
    RETURN GREATEST(0, LEAST(10, overall_score));
END;
$$ LANGUAGE plpgsql;

-- Main function to calculate all scores for a service
CREATE OR REPLACE FUNCTION calculate_service_scores() 
RETURNS TRIGGER AS $$
DECLARE
    privacy_score INT;
    trust_score INT;
    overall_score INT;
    service_id INT;
BEGIN
    -- Determine which service ID to use based on the trigger context and table
    IF TG_TABLE_NAME = 'Service' THEN
        IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
            service_id := NEW."id";
        END IF;
    ELSIF TG_TABLE_NAME = 'ServiceAttribute' THEN
        IF TG_OP = 'DELETE' THEN
            service_id := OLD."serviceId";
        ELSE -- INSERT or UPDATE
            service_id := NEW."serviceId";
        END IF;
    END IF;
    
    -- Calculate each score
    privacy_score := calculate_privacy_score(service_id);
    trust_score := calculate_trust_score(service_id);
    overall_score := calculate_overall_score(service_id, privacy_score, trust_score);

    -- Cap score if service is flagged as scam (verificationStatus = 'VERIFICATION_FAILED')
    IF (SELECT "verificationStatus" FROM "Service" WHERE "id" = service_id) = 'VERIFICATION_FAILED' THEN
        IF overall_score > 3 THEN
            overall_score := 3;
        ELSIF overall_score < 0 THEN
            overall_score := 0;
        END IF;
    END IF;

    -- Update the service with the new scores
    UPDATE "Service"
    SET 
        "privacyScore" = privacy_score,
        "trustScore" = trust_score,
        "overallScore" = overall_score
    WHERE "id" = service_id;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create trigger to recalculate scores when service is created or updated
CREATE TRIGGER service_score_update_trigger
    AFTER INSERT OR UPDATE
    ON "Service"
    FOR EACH ROW
    WHEN (pg_trigger_depth() < 2)  -- Prevent recursive triggering
    EXECUTE FUNCTION calculate_service_scores();

-- Create trigger to recalculate scores when service attributes change
CREATE TRIGGER service_attribute_change_trigger
    AFTER INSERT OR UPDATE OR DELETE
    ON "ServiceAttribute"
    FOR EACH ROW
    WHEN (pg_trigger_depth() < 2)  -- Prevent recursive triggering
    EXECUTE FUNCTION calculate_service_scores();

-- Function to queue score recalculation for all services with a specific attribute
CREATE OR REPLACE FUNCTION queue_service_score_recalculation_for_attribute()
RETURNS TRIGGER AS $$
DECLARE
    service_rec RECORD;
BEGIN
    -- Only trigger recalculation if relevant fields changed
    IF (TG_OP = 'UPDATE' AND (
        OLD."privacyPoints" != NEW."privacyPoints" OR
        OLD."trustPoints" != NEW."trustPoints" OR
        OLD."type" != NEW."type" OR
        OLD."category" != NEW."category"
    )) THEN
        -- Find all services that have this attribute and queue a recalculation job
        FOR service_rec IN 
            SELECT DISTINCT sa."serviceId" 
            FROM "ServiceAttribute" sa 
            WHERE sa."attributeId" = NEW.id
        LOOP
            -- Insert a job into the queue table
            -- ON CONFLICT clause ensures we don't queue the same service multiple times per transaction
            INSERT INTO "ServiceScoreRecalculationJob" ("serviceId", "createdAt", "processedAt")
            VALUES (service_rec."serviceId", NOW(), NULL)
            ON CONFLICT ("serviceId") DO UPDATE SET "processedAt" = NULL, "createdAt" = NOW();

        END LOOP;
    END IF;
    
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- Create constraint trigger to queue score recalculation when attributes are updated
DROP TRIGGER IF EXISTS attribute_change_trigger ON "Attribute";
CREATE CONSTRAINT TRIGGER attribute_change_trigger
    AFTER UPDATE
    ON "Attribute"
    DEFERRABLE INITIALLY DEFERRED
    FOR EACH ROW
    WHEN (pg_trigger_depth() < 2)
    EXECUTE FUNCTION queue_service_score_recalculation_for_attribute();
