<?php
/**
 * DBA NewsBot Dedupe — Company + Geography duplicate detection
 *
 * Validates candidate items (company + geography) before publishing.
 * Uses wp_options for history storage — no custom table needed.
 *
 * ═══════════════════════════════════════════════════════════════════════
 *  ZERO CONFLICT: Uses option key 'damalion_newsbot_history'
 *  Core Damalion has NO equivalent — no collision possible.
 * ═══════════════════════════════════════════════════════════════════════
 *
 * Features:
 *   - Same-day geography uniqueness (1 company per geography per day)
 *   - Same-day company uniqueness (exact, normalized, domain, fuzzy)
 *   - Cross-day cooldown (last N publish days — Mon/Wed/Fri friendly)
 *   - Self-pruning history (keeps only MAX_HISTORY_DAYS)
 *
 * @since 2.6.0
 */

if ( ! defined( 'ABSPATH' ) ) exit;

class DBA_NewsBot_Dedupe {

    // --- CONFIG ---
    const OPTION_KEY       = 'damalion_newsbot_history';
    const MAX_HISTORY_DAYS = 10;

    // Fuzzy matching thresholds
    const FUZZY_MIN_CHARS  = 6;   // don't fuzz-match very short names
    const FUZZY_SIMILARITY = 0.90; // 90 % similarity = treat as duplicate

    // Cross-day cooldown
    const COOLDOWN_PUBLISH_DAYS = 5; // avoid repeating across last 5 publish days (~2 weeks)

    // Geographies are now configurable from the dashboard (dba_pe_settings → active_geographies)
    // Fallback default if settings are empty
    private static $default_geos = array(
        'new-york', 'florida', 'colorado', 'texas', 'california', 'new-hampshire', 'arizona',
    );

    // Common legal suffixes to strip for normalization
    private static $suffixes = array(
        'inc', 'incorporated', 'corp', 'corporation', 'co', 'company',
        'llc', 'l.l.c', 'ltd', 'limited', 'plc', 'ag', 'sa', 'nv', 'n.v',
        'lp', 'l.p', 'llp', 'l.l.p',
        'group', 'holdings', 'holding', 'management', 'partners', 'partner', 'capital',
    );

    // Optional alias map (tune over time)
    private static $company_aliases = array(
        // 'the carlyle group' => 'carlyle',
        // 'carlyle group'     => 'carlyle',
    );

    // --- PUBLIC API ---

    /**
     * Validate a candidate item and reserve it if accepted.
     *
     * @param array $item {
     *     @type string $date         'YYYY-MM-DD' (defaults to today)
     *     @type string $geo          'Texas' or 'tx' etc.
     *     @type string $company_name 'P10, Inc.'
     *     @type string $official_url 'https://www.p10alts.com' (recommended)
     * }
     * @return array { 'ok' => bool, 'reason' => string, ... }
     */
    public static function validate_and_reserve( array $item ) {

        $date = ! empty( $item['date'] ) ? $item['date'] : current_time( 'Y-m-d' );

        $geo_key = self::normalize_geo( $item['geo'] ?? '' );
        if ( ! $geo_key || ! in_array( $geo_key, self::get_allowed_geos(), true ) ) {
            return self::reject( 'Geography not allowed or missing: ' . ( $item['geo'] ?? '' ) );
        }

        $company_raw = trim( (string) ( $item['company_name'] ?? '' ) );
        if ( $company_raw === '' ) {
            return self::reject( 'Missing company name' );
        }

        $company_key = self::normalize_company( $company_raw );

        // Best-practice: also use official domain as a stronger unique key if available
        $domain_key = self::domain_from_url( $item['official_url'] ?? '' );

        if ( $company_key === '' && $domain_key === '' ) {
            return self::reject( 'Company normalization failed and no official domain provided' );
        }

        $history = self::load_history();

        // Ensure day record exists
        if ( ! isset( $history['days'][ $date ] ) ) {
            $history['days'][ $date ] = array(
                'companies'    => array(),
                'company_keys' => array(),
                'domains'      => array(),
                'geos'         => array(),
                'created_at'   => time(),
            );
        }

        // --- SAME-DAY GEOGRAPHY DUPLICATE ---
        // If ALL active geos are already used today, allow reuse (different company).
        // This lets manual re-runs and retries create new content.
        $all_geos_used = ( count( array_unique( $history['days'][ $date ]['geos'] ) ) >= count( self::get_allowed_geos() ) );
        if ( ! $all_geos_used && in_array( $geo_key, $history['days'][ $date ]['geos'], true ) ) {
            return self::reject( "Duplicate geography for the day: {$geo_key}" );
        }

        // --- SAME-DAY COMPANY DUPLICATE (domain first, then normalized key) ---
        if ( $domain_key && in_array( $domain_key, $history['days'][ $date ]['domains'], true ) ) {
            return self::reject( "Duplicate company for the day (domain match): {$domain_key}" );
        }

        if ( $company_key && in_array( $company_key, $history['days'][ $date ]['company_keys'], true ) ) {
            return self::reject( "Duplicate company for the day (normalized match): {$company_key}" );
        }

        // --- SAME-DAY FUZZY DUPLICATE ---
        if ( $company_key ) {
            foreach ( $history['days'][ $date ]['company_keys'] as $existing_key ) {
                if ( self::is_fuzzy_duplicate( $company_key, $existing_key ) ) {
                    return self::reject( "Duplicate company for the day (fuzzy match): {$company_key} ~ {$existing_key}" );
                }
            }
        }

        // --- CROSS-DAY COOLDOWN CHECK (last X publish days) ---
        $recent_dates = self::get_recent_publish_dates( $history, $date, self::COOLDOWN_PUBLISH_DAYS );

        foreach ( $recent_dates as $d ) {
            // domain-based cooldown
            if ( $domain_key && ! empty( $history['days'][ $d ]['domains'] )
                && in_array( $domain_key, $history['days'][ $d ]['domains'], true ) ) {
                return self::reject( "Company blocked by cooldown (domain seen on {$d}): {$domain_key}" );
            }

            // normalized-key cooldown
            if ( $company_key && ! empty( $history['days'][ $d ]['company_keys'] )
                && in_array( $company_key, $history['days'][ $d ]['company_keys'], true ) ) {
                return self::reject( "Company blocked by cooldown (seen on {$d}): {$company_key}" );
            }

            // fuzzy cooldown
            if ( $company_key && ! empty( $history['days'][ $d ]['company_keys'] ) ) {
                foreach ( $history['days'][ $d ]['company_keys'] as $existing_key ) {
                    if ( self::is_fuzzy_duplicate( $company_key, $existing_key ) ) {
                        return self::reject( "Company blocked by cooldown (fuzzy match on {$d}): {$company_key} ~ {$existing_key}" );
                    }
                }
            }
        }

        // --- ACCEPT & RESERVE ---
        $history['days'][ $date ]['geos'][]      = $geo_key;
        $history['days'][ $date ]['companies'][]  = $company_raw;

        if ( $company_key ) {
            $history['days'][ $date ]['company_keys'][] = $company_key;
        }
        if ( $domain_key ) {
            $history['days'][ $date ]['domains'][] = $domain_key;
        }

        self::prune_history( $history );
        self::save_history( $history );

        return array(
            'ok'          => true,
            'reason'      => 'Accepted',
            'date'        => $date,
            'geo_key'     => $geo_key,
            'company_key' => $company_key,
            'domain_key'  => $domain_key,
        );
    }

    /**
     * Get allowed geographies list — reads from dashboard settings.
     * Returns canonical slugs (e.g., 'new-york', 'california').
     */
    public static function get_allowed_geos() {
        $pe_settings = get_option( 'dba_pe_settings', array() );
        $active      = $pe_settings['active_geographies'] ?? array();

        // active_geographies stores display names ('New York', 'California')
        // Convert to canonical slugs
        if ( ! empty( $active ) ) {
            $slugs = array();
            foreach ( $active as $name ) {
                $slug = self::normalize_geo( $name );
                if ( $slug ) $slugs[] = $slug;
            }
            return ! empty( $slugs ) ? $slugs : self::$default_geos;
        }

        return self::$default_geos;
    }

    /**
     * Get allowed geographies as display names ('New York', 'Florida').
     * Used by generator for prompts and roundup headings.
     */
    public static function get_active_geo_names() {
        $pe_settings = get_option( 'dba_pe_settings', array() );
        $active      = $pe_settings['active_geographies'] ?? array();

        if ( ! empty( $active ) ) {
            return $active;
        }

        // Fallback: default 7 as display names
        return array( 'New York', 'Florida', 'Colorado', 'Texas', 'California', 'New Hampshire', 'Arizona' );
    }

    /**
     * Get the full history (for admin/debug display).
     */
    public static function get_history() {
        return self::load_history();
    }

    /**
     * Clear all history (manual reset).
     */
    public static function clear_history() {
        delete_option( self::OPTION_KEY );
    }

    // --- HELPERS ---

    /**
     * Get the full list of all US states (for admin checkboxes).
     * Returns display name => canonical slug.
     */
    public static function get_all_us_states() {
        return array(
            'Alabama'              => 'alabama',
            'Alaska'               => 'alaska',
            'Arizona'              => 'arizona',
            'Arkansas'             => 'arkansas',
            'California'           => 'california',
            'Colorado'             => 'colorado',
            'Connecticut'          => 'connecticut',
            'Delaware'             => 'delaware',
            'District of Columbia' => 'district-of-columbia',
            'Florida'              => 'florida',
            'Georgia'              => 'georgia',
            'Hawaii'               => 'hawaii',
            'Idaho'                => 'idaho',
            'Illinois'             => 'illinois',
            'Indiana'              => 'indiana',
            'Iowa'                 => 'iowa',
            'Kansas'               => 'kansas',
            'Kentucky'             => 'kentucky',
            'Louisiana'            => 'louisiana',
            'Maine'                => 'maine',
            'Maryland'             => 'maryland',
            'Massachusetts'        => 'massachusetts',
            'Michigan'             => 'michigan',
            'Minnesota'            => 'minnesota',
            'Mississippi'          => 'mississippi',
            'Missouri'             => 'missouri',
            'Montana'              => 'montana',
            'Nebraska'             => 'nebraska',
            'Nevada'               => 'nevada',
            'New Hampshire'        => 'new-hampshire',
            'New Jersey'           => 'new-jersey',
            'New Mexico'           => 'new-mexico',
            'New York'             => 'new-york',
            'North Carolina'       => 'north-carolina',
            'North Dakota'         => 'north-dakota',
            'Ohio'                 => 'ohio',
            'Oklahoma'             => 'oklahoma',
            'Oregon'               => 'oregon',
            'Pennsylvania'         => 'pennsylvania',
            'Rhode Island'         => 'rhode-island',
            'South Carolina'       => 'south-carolina',
            'South Dakota'         => 'south-dakota',
            'Tennessee'            => 'tennessee',
            'Texas'                => 'texas',
            'Utah'                 => 'utah',
            'Vermont'              => 'vermont',
            'Virginia'             => 'virginia',
            'Washington'           => 'washington',
            'West Virginia'        => 'west-virginia',
            'Wisconsin'            => 'wisconsin',
            'Wyoming'              => 'wyoming',
        );
    }

    /**
     * Get the official .gov URL for a US state.
     * Returns the URL or empty string if not found.
     *
     * @param string $state Display name (e.g., 'New York') or slug (e.g., 'new-york').
     * @return string       Official .gov URL.
     */
    public static function get_gov_url( string $state ) {
        $gov_urls = array(
            'alabama'              => 'https://www.alabama.gov',
            'alaska'               => 'https://www.alaska.gov',
            'arizona'              => 'https://www.az.gov',
            'arkansas'             => 'https://www.arkansas.gov',
            'california'           => 'https://www.ca.gov',
            'colorado'             => 'https://www.colorado.gov',
            'connecticut'          => 'https://portal.ct.gov',
            'delaware'             => 'https://www.delaware.gov',
            'district-of-columbia' => 'https://www.dc.gov',
            'florida'              => 'https://www.myflorida.com',
            'georgia'              => 'https://www.georgia.gov',
            'hawaii'               => 'https://www.hawaii.gov',
            'idaho'                => 'https://www.idaho.gov',
            'illinois'             => 'https://www.illinois.gov',
            'indiana'              => 'https://www.in.gov',
            'iowa'                 => 'https://www.iowa.gov',
            'kansas'               => 'https://www.kansas.gov',
            'kentucky'             => 'https://www.kentucky.gov',
            'louisiana'            => 'https://www.louisiana.gov',
            'maine'                => 'https://www.maine.gov',
            'maryland'             => 'https://www.maryland.gov',
            'massachusetts'        => 'https://www.mass.gov',
            'michigan'             => 'https://www.michigan.gov',
            'minnesota'            => 'https://www.mn.gov',
            'mississippi'          => 'https://www.ms.gov',
            'missouri'             => 'https://www.mo.gov',
            'montana'              => 'https://www.mt.gov',
            'nebraska'             => 'https://www.nebraska.gov',
            'nevada'               => 'https://www.nv.gov',
            'new-hampshire'        => 'https://www.nh.gov',
            'new-jersey'           => 'https://www.nj.gov',
            'new-mexico'           => 'https://www.nm.gov',
            'new-york'             => 'https://www.ny.gov',
            'north-carolina'       => 'https://www.nc.gov',
            'north-dakota'         => 'https://www.nd.gov',
            'ohio'                 => 'https://www.ohio.gov',
            'oklahoma'             => 'https://www.ok.gov',
            'oregon'               => 'https://www.oregon.gov',
            'pennsylvania'         => 'https://www.pa.gov',
            'rhode-island'         => 'https://www.ri.gov',
            'south-carolina'       => 'https://www.sc.gov',
            'south-dakota'         => 'https://www.sd.gov',
            'tennessee'            => 'https://www.tn.gov',
            'texas'                => 'https://www.texas.gov',
            'utah'                 => 'https://www.utah.gov',
            'vermont'              => 'https://www.vermont.gov',
            'virginia'             => 'https://www.virginia.gov',
            'washington'           => 'https://www.wa.gov',
            'west-virginia'        => 'https://www.wv.gov',
            'wisconsin'            => 'https://www.wisconsin.gov',
            'wyoming'              => 'https://www.wyo.gov',
        );

        $slug = self::normalize_geo( $state );
        return $gov_urls[ $slug ] ?? '';
    }

    private static function reject( string $reason ) {
        return array( 'ok' => false, 'reason' => $reason );
    }

    private static function load_history() {
        $history = get_option( self::OPTION_KEY, array() );
        if ( ! is_array( $history ) ) $history = array();
        if ( empty( $history['days'] ) || ! is_array( $history['days'] ) ) $history['days'] = array();
        return $history;
    }

    private static function save_history( array $history ) {
        update_option( self::OPTION_KEY, $history, false );
    }

    private static function prune_history( array &$history ) {
        $dates = array_keys( $history['days'] );
        rsort( $dates ); // newest first
        $keep = array_slice( $dates, 0, self::MAX_HISTORY_DAYS );
        $history['days'] = array_intersect_key( $history['days'], array_flip( $keep ) );
    }

    /**
     * Return the most recent publish dates before $current_date, limited to $limit.
     * This is NOT calendar days — it is "publish days" present in history.
     */
    private static function get_recent_publish_dates( array $history, string $current_date, int $limit ) {
        $dates = array_keys( $history['days'] );
        rsort( $dates );
        $out = array();
        foreach ( $dates as $d ) {
            if ( $d < $current_date ) {
                $out[] = $d;
                if ( count( $out ) >= $limit ) break;
            }
        }
        return $out;
    }

    public static function normalize_geo( string $raw ) {
        $s = strtolower( trim( $raw ) );
        $s = preg_replace( '/[.,\/()]/', ' ', $s );
        $s = preg_replace( '/\s+/', ' ', $s );
        $s = trim( $s );

        // Full US states + abbreviations + major city aliases
        $aliases = array(
            // Original 7 states + aliases
            'ny'             => 'new-york',
            'nyc'            => 'new-york',
            'new york'       => 'new-york',
            'new york city'  => 'new-york',
            'manhattan'      => 'new-york',
            'brooklyn'       => 'new-york',
            'fl'             => 'florida',
            'florida'        => 'florida',
            'miami'          => 'florida',
            'orlando'        => 'florida',
            'tampa'          => 'florida',
            'jacksonville'   => 'florida',
            'co'             => 'colorado',
            'colorado'       => 'colorado',
            'denver'         => 'colorado',
            'boulder'        => 'colorado',
            'tx'             => 'texas',
            'texas'          => 'texas',
            'austin'         => 'texas',
            'dallas'         => 'texas',
            'houston'        => 'texas',
            'san antonio'    => 'texas',
            'fort worth'     => 'texas',
            'ca'             => 'california',
            'california'     => 'california',
            'los angeles'    => 'california',
            'san francisco'  => 'california',
            'sf'             => 'california',
            'silicon valley' => 'california',
            'san diego'      => 'california',
            'san jose'       => 'california',
            'nh'             => 'new-hampshire',
            'new hampshire'  => 'new-hampshire',
            'az'             => 'arizona',
            'arizona'        => 'arizona',
            'phoenix'        => 'arizona',
            'scottsdale'     => 'arizona',
            'tucson'         => 'arizona',

            // All other US states + abbreviations
            'al'             => 'alabama',
            'alabama'        => 'alabama',
            'birmingham'     => 'alabama',
            'ak'             => 'alaska',
            'alaska'         => 'alaska',
            'ar'             => 'arkansas',
            'arkansas'       => 'arkansas',
            'ct'             => 'connecticut',
            'connecticut'    => 'connecticut',
            'hartford'       => 'connecticut',
            'stamford'       => 'connecticut',
            'de'             => 'delaware',
            'delaware'       => 'delaware',
            'dc'             => 'district-of-columbia',
            'district of columbia' => 'district-of-columbia',
            'washington dc'  => 'district-of-columbia',
            'washington d c' => 'district-of-columbia',
            'ga'             => 'georgia',
            'georgia'        => 'georgia',
            'atlanta'        => 'georgia',
            'hi'             => 'hawaii',
            'hawaii'         => 'hawaii',
            'id'             => 'idaho',
            'idaho'          => 'idaho',
            'il'             => 'illinois',
            'illinois'       => 'illinois',
            'chicago'        => 'illinois',
            'in'             => 'indiana',
            'indiana'        => 'indiana',
            'indianapolis'   => 'indiana',
            'ia'             => 'iowa',
            'iowa'           => 'iowa',
            'ks'             => 'kansas',
            'kansas'         => 'kansas',
            'ky'             => 'kentucky',
            'kentucky'       => 'kentucky',
            'la'             => 'louisiana',
            'louisiana'      => 'louisiana',
            'new orleans'    => 'louisiana',
            'me'             => 'maine',
            'maine'          => 'maine',
            'md'             => 'maryland',
            'maryland'       => 'maryland',
            'baltimore'      => 'maryland',
            'ma'             => 'massachusetts',
            'massachusetts'  => 'massachusetts',
            'boston'          => 'massachusetts',
            'mi'             => 'michigan',
            'michigan'       => 'michigan',
            'detroit'        => 'michigan',
            'mn'             => 'minnesota',
            'minnesota'      => 'minnesota',
            'minneapolis'    => 'minnesota',
            'ms'             => 'mississippi',
            'mississippi'    => 'mississippi',
            'mo'             => 'missouri',
            'missouri'       => 'missouri',
            'st louis'       => 'missouri',
            'kansas city'    => 'missouri',
            'mt'             => 'montana',
            'montana'        => 'montana',
            'ne'             => 'nebraska',
            'nebraska'       => 'nebraska',
            'nv'             => 'nevada',
            'nevada'         => 'nevada',
            'las vegas'      => 'nevada',
            'nj'             => 'new-jersey',
            'new jersey'     => 'new-jersey',
            'nm'             => 'new-mexico',
            'new mexico'     => 'new-mexico',
            'nc'             => 'north-carolina',
            'north carolina' => 'north-carolina',
            'charlotte'      => 'north-carolina',
            'raleigh'        => 'north-carolina',
            'nd'             => 'north-dakota',
            'north dakota'   => 'north-dakota',
            'oh'             => 'ohio',
            'ohio'           => 'ohio',
            'columbus'       => 'ohio',
            'cleveland'      => 'ohio',
            'cincinnati'     => 'ohio',
            'ok'             => 'oklahoma',
            'oklahoma'       => 'oklahoma',
            'or'             => 'oregon',
            'oregon'         => 'oregon',
            'portland'       => 'oregon',
            'pa'             => 'pennsylvania',
            'pennsylvania'   => 'pennsylvania',
            'philadelphia'   => 'pennsylvania',
            'pittsburgh'     => 'pennsylvania',
            'ri'             => 'rhode-island',
            'rhode island'   => 'rhode-island',
            'sc'             => 'south-carolina',
            'south carolina' => 'south-carolina',
            'charleston'     => 'south-carolina',
            'sd'             => 'south-dakota',
            'south dakota'   => 'south-dakota',
            'tn'             => 'tennessee',
            'tennessee'      => 'tennessee',
            'nashville'      => 'tennessee',
            'memphis'        => 'tennessee',
            'ut'             => 'utah',
            'utah'           => 'utah',
            'salt lake city' => 'utah',
            'vt'             => 'vermont',
            'vermont'        => 'vermont',
            'va'             => 'virginia',
            'virginia'       => 'virginia',
            'richmond'       => 'virginia',
            'wa'             => 'washington',
            'washington'     => 'washington',
            'seattle'        => 'washington',
            'wv'             => 'west-virginia',
            'west virginia'  => 'west-virginia',
            'wi'             => 'wisconsin',
            'wisconsin'      => 'wisconsin',
            'milwaukee'      => 'wisconsin',
            'wy'             => 'wyoming',
            'wyoming'        => 'wyoming',
        );

        return $aliases[ $s ] ?? $s;
    }

    public static function normalize_company( string $raw ) {
        $s = strtolower( trim( $raw ) );
        $s = str_replace( '&', ' and ', $s );
        $s = preg_replace( '/[^a-z0-9\s\-]/', ' ', $s );
        $s = preg_replace( '/\s+/', ' ', $s );
        $s = trim( $s );

        if ( isset( self::$company_aliases[ $s ] ) ) {
            return self::$company_aliases[ $s ];
        }

        $tokens = explode( ' ', $s );
        $clean  = array();
        foreach ( $tokens as $t ) {
            if ( $t === '' ) continue;
            if ( in_array( $t, self::$suffixes, true ) ) continue;
            $clean[] = $t;
        }

        return trim( implode( ' ', $clean ) );
    }

    private static function domain_from_url( string $url ) {
        $url = trim( $url );
        if ( $url === '' ) return '';
        $host = wp_parse_url( $url, PHP_URL_HOST );
        if ( ! $host ) return '';
        $host = strtolower( $host );
        $host = preg_replace( '/^www\./', '', $host );
        return $host;
    }

    /**
     * Fuzzy duplicate check using normalized Levenshtein similarity.
     */
    private static function is_fuzzy_duplicate( string $a, string $b ) {
        $a = trim( $a );
        $b = trim( $b );
        if ( $a === '' || $b === '' ) return false;
        if ( $a === $b ) return true;

        // avoid false positives on very short names (e.g., "P10", "BP")
        if ( strlen( $a ) < self::FUZZY_MIN_CHARS || strlen( $b ) < self::FUZZY_MIN_CHARS ) {
            return false;
        }

        $maxLen = max( strlen( $a ), strlen( $b ) );
        if ( $maxLen === 0 ) return false;

        $dist       = levenshtein( $a, $b );
        $similarity = 1 - ( $dist / $maxLen );

        return ( $similarity >= self::FUZZY_SIMILARITY );
    }
}
