"""
Smart Pounce - Domain Auction Aggregator

This module provides auction data from our database of scraped listings.
Data is scraped from public auction platforms - NO APIS used.

Data Sources (Web Scraping):
- ExpiredDomains.net (aggregator)
- GoDaddy Auctions (public listings)
- Sedo (public search)
- NameJet (public auctions)

PLUS Pounce Direct Listings (user-created marketplace):
- DNS-verified owner listings
- Instant buy option
- 0% commission

IMPORTANT:
- All data comes from web scraping of public pages
- No mock data - everything is real scraped data
- Data is cached in PostgreSQL/SQLite for performance
- Scraper runs on schedule (see scheduler.py)

Legal Note (Switzerland):
- No escrow/payment handling = no GwG/FINMA requirements
- Users click through to external platforms
- We only provide market intelligence
"""
import logging
from datetime import datetime, timedelta
from typing import Optional, List
from itertools import groupby
from fastapi import APIRouter, Depends, Query, HTTPException
from pydantic import BaseModel
from sqlalchemy import select, func, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession

from app.database import get_db
from app.api.deps import get_current_user, get_current_user_optional
from app.models.user import User
from app.models.auction import DomainAuction, AuctionScrapeLog
from app.models.listing import DomainListing, ListingStatus, VerificationStatus
from app.services.valuation import valuation_service
from app.services.auction_scraper import auction_scraper

logger = logging.getLogger(__name__)
router = APIRouter()


# ============== Schemas ==============

class AuctionValuation(BaseModel):
    """Valuation details for an auction."""
    estimated_value: float
    value_ratio: float
    potential_profit: float
    confidence: str
    valuation_formula: str


class AuctionListing(BaseModel):
    """A domain auction listing from the database."""
    domain: str
    platform: str
    platform_url: str
    current_bid: float
    currency: str
    num_bids: int
    end_time: datetime
    time_remaining: str
    buy_now_price: Optional[float] = None
    reserve_met: Optional[bool] = None
    traffic: Optional[int] = None
    age_years: Optional[int] = None
    tld: str
    affiliate_url: str
    valuation: Optional[AuctionValuation] = None
    
    class Config:
        from_attributes = True
        # Serialize datetimes as ISO format with UTC timezone suffix
        json_encoders = {
            datetime: lambda v: v.isoformat() + "Z" if v else None
        }


class AuctionSearchResponse(BaseModel):
    """Response for auction search."""
    auctions: List[AuctionListing]
    total: int
    platforms_searched: List[str]
    last_updated: datetime
    data_source: str = "scraped"
    valuation_note: str = (
        "Values are estimated using our algorithm: "
        "$50 × Length × TLD × Keyword × Brand factors. "
        "See /portfolio/valuation/{domain} for detailed breakdown."
    )
    
    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat() + "Z" if v else None
        }


class PlatformStats(BaseModel):
    """Statistics for an auction platform."""
    platform: str
    active_auctions: int
    avg_bid: float
    ending_soon: int


class ScrapeStatus(BaseModel):
    """Status of auction scraping."""
    last_scrape: Optional[datetime]
    total_auctions: int
    platforms: List[str]
    next_scrape: Optional[datetime]
    
    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat() + "Z" if v else None
        }


class MarketFeedItem(BaseModel):
    """Unified market feed item - combines auctions and Pounce Direct listings."""
    id: str
    domain: str
    tld: str
    price: float
    currency: str = "USD"
    price_type: str  # "bid" or "fixed"
    status: str  # "auction" or "instant"
    
    # Source info
    source: str  # "Pounce", "GoDaddy", "Sedo", etc.
    is_pounce: bool = False
    verified: bool = False
    
    # Auction-specific
    time_remaining: Optional[str] = None
    end_time: Optional[datetime] = None
    num_bids: Optional[int] = None
    
    # Pounce Direct specific
    slug: Optional[str] = None
    seller_verified: bool = False
    
    # URLs
    url: str  # Internal for Pounce, external for auctions
    is_external: bool = True
    
    # Scoring
    pounce_score: int = 50
    
    # Valuation (optional)
    valuation: Optional[AuctionValuation] = None
    
    class Config:
        from_attributes = True
        json_encoders = {
            datetime: lambda v: v.isoformat() + "Z" if v else None
        }


class MarketFeedResponse(BaseModel):
    """Response for unified market feed."""
    items: List[MarketFeedItem]
    total: int
    pounce_direct_count: int
    auction_count: int
    sources: List[str]
    last_updated: datetime
    filters_applied: dict = {}
    
    class Config:
        json_encoders = {
            datetime: lambda v: v.isoformat() + "Z" if v else None
        }


# ============== Helper Functions ==============

def _format_time_remaining(end_time: datetime, now: Optional[datetime] = None) -> str:
    """Format time remaining in human-readable format."""
    ref = now or datetime.utcnow()
    delta = end_time - ref
    
    # Small grace window to avoid displaying "Ended" due to request processing time.
    # If an auction ends within the next ~2 seconds, we show "0m".
    if delta.total_seconds() <= -2:
        return "Ended"
    
    hours = int(delta.total_seconds() // 3600)
    minutes = int((delta.total_seconds() % 3600) // 60)
    
    if hours > 24:
        days = hours // 24
        return f"{days}d {hours % 24}h"
    elif hours > 0:
        return f"{hours}h {minutes}m"
    else:
        return f"{max(minutes, 0)}m"


def _get_affiliate_url(platform: str, domain: str, auction_url: str) -> str:
    """Get affiliate URL for a platform - links directly to the auction page with affiliate tracking."""
    # SEDO SPECIAL CASE: Always use direct Sedo link with partner ID
    # This ensures we get affiliate revenue even from scraped data
    if platform == "Sedo":
        return f"https://sedo.com/search/details/?domain={domain}&partnerid=335830"
    
    # Import here to avoid circular imports
    from app.services.hidden_api_scrapers import build_affiliate_url
    
    # Try to build affiliate URL first (includes partner IDs)
    affiliate_url = build_affiliate_url(platform, domain, auction_url)
    if affiliate_url:
        return affiliate_url
    
    # Use the scraped auction URL directly if available
    if auction_url and auction_url.startswith("http"):
        return auction_url
    
    # Fallback to platform-specific search/listing pages (without affiliate tracking)
    platform_urls = {
        "GoDaddy": f"https://auctions.godaddy.com/trpItemListing.aspx?domain={domain}",
        "NameJet": f"https://www.namejet.com/Pages/Auctions/BackorderSearch.aspx?q={domain}",
        "DropCatch": f"https://www.dropcatch.com/domain/{domain}",
        "ExpiredDomains": f"https://www.expireddomains.net/domain-name-search/?q={domain}",
        "Afternic": f"https://www.afternic.com/search?k={domain}",
        "Dynadot": f"https://www.dynadot.com/market/auction/{domain}",
        "Porkbun": f"https://porkbun.com/checkout/search?q={domain}",
    }
    return platform_urls.get(platform, f"https://www.google.com/search?q={domain}+domain+auction")


async def _convert_to_listing(
    auction: DomainAuction, 
    db: AsyncSession, 
    include_valuation: bool = True,
    now: Optional[datetime] = None,
) -> AuctionListing:
    """Convert database auction to API response."""
    valuation_data = None
    
    if include_valuation:
        try:
            result = await valuation_service.estimate_value(auction.domain, db, save_result=False)
            
            if "error" not in result:
                estimated_value = result["estimated_value"]
                value_ratio = round(estimated_value / auction.current_bid, 2) if auction.current_bid > 0 else 99
                
                valuation_data = AuctionValuation(
                    estimated_value=estimated_value,
                    value_ratio=value_ratio,
                    potential_profit=round(estimated_value - auction.current_bid, 2),
                    confidence=result.get("confidence", "medium"),
                    valuation_formula=result.get("calculation", {}).get("formula", "N/A"),
                )
        except Exception as e:
            logger.error(f"Valuation error for {auction.domain}: {e}")
    
    return AuctionListing(
        domain=auction.domain,
        platform=auction.platform,
        platform_url=auction.auction_url or "",
        current_bid=auction.current_bid,
        currency=auction.currency,
        num_bids=auction.num_bids,
        end_time=auction.end_time,
        time_remaining=_format_time_remaining(auction.end_time, now=now),
        buy_now_price=auction.buy_now_price,
        reserve_met=auction.reserve_met,
        traffic=auction.traffic,
        age_years=auction.age_years,
        tld=auction.tld,
        affiliate_url=_get_affiliate_url(auction.platform, auction.domain, auction.auction_url),
        valuation=valuation_data,
    )


# ============== Endpoints ==============

@router.get("", response_model=AuctionSearchResponse)
async def search_auctions(
    keyword: Optional[str] = Query(None, description="Search keyword in domain names"),
    tld: Optional[str] = Query(None, description="Filter by TLD (e.g., 'com', 'io')"),
    platform: Optional[str] = Query(None, description="Filter by platform"),
    min_bid: Optional[float] = Query(None, ge=0, description="Minimum current bid"),
    max_bid: Optional[float] = Query(None, ge=0, description="Maximum current bid"),
    ending_soon: bool = Query(False, description="Only show auctions ending in < 1 hour"),
    sort_by: str = Query("ending", enum=["ending", "bid_asc", "bid_desc", "bids", "value_ratio"]),
    limit: int = Query(20, le=100),
    offset: int = Query(0, ge=0),
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """
    Search domain auctions from our scraped database.
    
    All data comes from web scraping of public auction pages.
    NO mock data - everything is real scraped data.
    
    Data Sources:
    - ExpiredDomains.net (aggregator)
    - GoDaddy Auctions (coming soon)
    - Sedo (coming soon)
    - NameJet (coming soon)
    
    Smart Pounce Strategy:
    - Look for value_ratio > 1.0 (estimated value exceeds current bid)
    - Focus on auctions ending soon with low bid counts
    """
    # Build query - ONLY show active auctions that haven't ended yet
    now = datetime.utcnow()
    query = select(DomainAuction).where(
        and_(
            DomainAuction.is_active == True,
            DomainAuction.end_time > now  # ← KRITISCH: Nur Auktionen die noch laufen!
        )
    )
    
    # VANITY FILTER: For public (non-logged-in) users, only show premium-looking domains
    # This ensures the first impression is high-quality, not spam domains
    if current_user is None:
        # Premium TLDs only (no .cc, .website, .info spam clusters)
        premium_tlds = ['com', 'io', 'ai', 'co', 'de', 'ch', 'net', 'org', 'app', 'dev', 'xyz']
        query = query.where(DomainAuction.tld.in_(premium_tlds))
        # No domains with more than 15 characters (excluding TLD)
        # Note: We filter further in Python for complex rules
    
    if keyword:
        query = query.where(DomainAuction.domain.ilike(f"%{keyword}%"))
    
    if tld:
        query = query.where(DomainAuction.tld == tld.lower().lstrip("."))
    
    if platform:
        query = query.where(DomainAuction.platform == platform)
    
    if min_bid is not None:
        query = query.where(DomainAuction.current_bid >= min_bid)
    
    if max_bid is not None:
        query = query.where(DomainAuction.current_bid <= max_bid)
    
    if ending_soon:
        cutoff = datetime.utcnow() + timedelta(hours=1)
        query = query.where(DomainAuction.end_time <= cutoff)
    
    # Count total
    count_query = select(func.count()).select_from(query.subquery())
    total_result = await db.execute(count_query)
    total = total_result.scalar() or 0
    
    # Sort
    if sort_by == "ending":
        query = query.order_by(DomainAuction.end_time.asc())
    elif sort_by == "bid_asc":
        query = query.order_by(DomainAuction.current_bid.asc())
    elif sort_by == "bid_desc":
        query = query.order_by(DomainAuction.current_bid.desc())
    elif sort_by == "bids":
        query = query.order_by(DomainAuction.num_bids.desc())
    else:
        query = query.order_by(DomainAuction.end_time.asc())
    
    # Pagination
    query = query.offset(offset).limit(limit)
    
    result = await db.execute(query)
    auctions = list(result.scalars().all())
    
    # VANITY FILTER PART 2: Apply Python-side filtering for public users
    # This ensures only premium-looking domains are shown to non-logged-in users
    if current_user is None:
        def is_premium_domain(domain_name: str) -> bool:
            """Check if a domain looks premium/professional"""
            # Extract just the domain part (without TLD)
            parts = domain_name.rsplit('.', 1)
            name = parts[0] if parts else domain_name
            
            # Rule 1: No more than 15 characters
            if len(name) > 15:
                return False
            
            # Rule 2: No more than 1 hyphen
            if name.count('-') > 1:
                return False
            
            # Rule 3: No more than 2 digits total
            digit_count = sum(1 for c in name if c.isdigit())
            if digit_count > 2:
                return False
            
            # Rule 4: Must be at least 3 characters
            if len(name) < 3:
                return False
            
            # Rule 5: No random-looking strings (too many consonants in a row)
            consonants = 'bcdfghjklmnpqrstvwxyz'
            consonant_streak = 0
            max_streak = 0
            for c in name.lower():
                if c in consonants:
                    consonant_streak += 1
                    max_streak = max(max_streak, consonant_streak)
                else:
                    consonant_streak = 0
            if max_streak > 4:
                return False
            
            return True
        
        auctions = [a for a in auctions if is_premium_domain(a.domain)]
    
    # Convert to response with valuations
    listings = []
    for auction in auctions:
        listing = await _convert_to_listing(auction, db, include_valuation=True, now=now)
        listings.append(listing)
    
    # Sort by value_ratio if requested (after valuation)
    if sort_by == "value_ratio":
        listings.sort(
            key=lambda x: x.valuation.value_ratio if x.valuation else 0,
            reverse=True
        )
    
    # Get platforms searched
    platforms_result = await db.execute(
        select(DomainAuction.platform).distinct()
    )
    platforms = [p for (p,) in platforms_result.all()]
    
    # Get last update time
    last_update_result = await db.execute(
        select(func.max(DomainAuction.updated_at))
    )
    last_updated = last_update_result.scalar() or datetime.utcnow()
    
    return AuctionSearchResponse(
        auctions=listings,
        total=total,
        platforms_searched=platforms or ["No data yet - scrape pending"],
        last_updated=last_updated,
        data_source="scraped from public auction sites",
    )


@router.get("/ending-soon", response_model=List[AuctionListing])
async def get_ending_soon(
    hours: int = Query(1, ge=1, le=24, description="Hours until end"),
    limit: int = Query(10, le=50),
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """
    Get auctions ending soon - best opportunities for sniping.
    
    Data is scraped from public auction sites - no mock data.
    """
    now = datetime.utcnow()
    cutoff = now + timedelta(hours=hours)
    
    query = (
        select(DomainAuction)
        .where(
            and_(
                DomainAuction.is_active == True,
                DomainAuction.end_time <= cutoff,
                DomainAuction.end_time > now,
            )
        )
        .order_by(DomainAuction.end_time.asc())
        .limit(limit)
    )
    
    result = await db.execute(query)
    auctions = list(result.scalars().all())
    
    listings = []
    for auction in auctions:
        listing = await _convert_to_listing(auction, db, include_valuation=True, now=now)
        listings.append(listing)
    
    return listings


@router.get("/hot", response_model=List[AuctionListing])
async def get_hot_auctions(
    limit: int = Query(10, le=50),
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """
    Get hottest auctions by bidding activity.
    
    Data is scraped from public auction sites - no mock data.
    """
    now = datetime.utcnow()
    query = (
        select(DomainAuction)
        .where(
            and_(
                DomainAuction.is_active == True,
                DomainAuction.end_time > now  # Only show active auctions
            )
        )
        .order_by(DomainAuction.num_bids.desc())
        .limit(limit)
    )
    
    result = await db.execute(query)
    auctions = list(result.scalars().all())
    
    listings = []
    for auction in auctions:
        listing = await _convert_to_listing(auction, db, include_valuation=True, now=now)
        listings.append(listing)
    
    return listings


@router.get("/stats", response_model=List[PlatformStats])
async def get_platform_stats(
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """
    Get statistics for each auction platform.
    
    Data is scraped from public auction sites - no mock data.
    """
    now = datetime.utcnow()
    # Get stats per platform
    stats_query = (
        select(
            DomainAuction.platform,
            func.count(DomainAuction.id).label("count"),
            func.avg(DomainAuction.current_bid).label("avg_bid"),
        )
        .where(
            and_(
                DomainAuction.is_active == True,
                DomainAuction.end_time > now,
            )
        )
        .group_by(DomainAuction.platform)
    )
    
    result = await db.execute(stats_query)
    platform_data = result.all()
    
    # Get ending soon counts
    cutoff = now + timedelta(hours=1)
    ending_query = (
        select(
            DomainAuction.platform,
            func.count(DomainAuction.id).label("ending_count"),
        )
        .where(
            and_(
                DomainAuction.is_active == True,
                DomainAuction.end_time > now,
                DomainAuction.end_time <= cutoff,
            )
        )
        .group_by(DomainAuction.platform)
    )
    
    ending_result = await db.execute(ending_query)
    ending_data = {p: c for p, c in ending_result.all()}
    
    stats = []
    for platform, count, avg_bid in platform_data:
        stats.append(PlatformStats(
            platform=platform,
            active_auctions=count,
            avg_bid=round(avg_bid or 0, 2),
            ending_soon=ending_data.get(platform, 0),
        ))
    
    return sorted(stats, key=lambda x: x.active_auctions, reverse=True)


@router.get("/scrape-status", response_model=ScrapeStatus)
async def get_scrape_status(
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """Get status of auction scraping."""
    now = datetime.utcnow()
    # Get last successful scrape
    last_scrape_query = (
        select(AuctionScrapeLog)
        .where(AuctionScrapeLog.status == "success")
        .order_by(AuctionScrapeLog.completed_at.desc())
        .limit(1)
    )
    result = await db.execute(last_scrape_query)
    last_log = result.scalar_one_or_none()
    
    # Get total auctions
    total_query = select(func.count(DomainAuction.id)).where(
        and_(
            DomainAuction.is_active == True,
            DomainAuction.end_time > now,
        )
    )
    total_result = await db.execute(total_query)
    total = total_result.scalar() or 0
    
    # Get platforms
    platforms_result = await db.execute(
        select(DomainAuction.platform).distinct()
    )
    platforms = [p for (p,) in platforms_result.all()]
    
    return ScrapeStatus(
        last_scrape=last_log.completed_at if last_log else None,
        total_auctions=total,
        platforms=platforms or ["Pending initial scrape"],
        next_scrape=datetime.utcnow() + timedelta(hours=1),  # Approximation
    )


@router.post("/trigger-scrape")
async def trigger_scrape(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    Manually trigger auction scraping (admin only for now).
    
    In production, this runs automatically every hour.
    """
    try:
        result = await auction_scraper.scrape_all_platforms(db)
        return {
            "status": "success",
            "message": "Scraping completed",
            "result": result,
        }
    except Exception as e:
        logger.error(f"Manual scrape failed: {e}")
        raise HTTPException(status_code=500, detail=f"Scrape failed: {str(e)}")


@router.get("/sedo")
async def get_sedo_listings(
    keyword: Optional[str] = Query(None, description="Search keyword"),
    tld: Optional[str] = Query(None, description="Filter by TLD"),
    limit: int = Query(50, le=100),
    current_user: Optional[User] = Depends(get_current_user_optional),
):
    """
    Get live domain listings from Sedo marketplace.
    
    Returns real-time data from Sedo API with affiliate tracking.
    All links include Pounce partner ID for commission tracking.
    """
    from app.services.sedo_api import sedo_client
    
    if not sedo_client.is_configured:
        return {
            "items": [],
            "error": "Sedo API not configured",
            "source": "sedo"
        }
    
    try:
        listings = await sedo_client.get_listings_for_display(
            keyword=keyword,
            tld=tld,
            page_size=limit
        )
        
        return {
            "items": listings,
            "count": len(listings),
            "source": "sedo",
            "affiliate_note": "All links include Pounce partner ID for commission tracking"
        }
    except Exception as e:
        logger.error(f"Sedo API error: {e}")
        return {
            "items": [],
            "error": str(e),
            "source": "sedo"
        }


@router.get("/opportunities")
async def get_smart_opportunities(
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """
    Smart Pounce Algorithm - Find the best auction opportunities.
    
    Analyzes auction data to find sweet spots:
    - Auctions ending soon (snipe potential)
    - Low bid counts (overlooked gems)
    - Good price points
    
    Opportunity Score = time_urgency × competition_factor × price_factor
    """
    # Get active auctions
    now = datetime.utcnow()
    query = (
        select(DomainAuction)
        .where(
            and_(
                DomainAuction.is_active == True,
                DomainAuction.end_time > now,
            )
        )
        .order_by(DomainAuction.end_time.asc())
        .limit(100)
    )
    
    result = await db.execute(query)
    auctions = list(result.scalars().all())
    
    if not auctions:
        return {
            "opportunities": [],
            "message": "No active auctions found.",
            "strategy_tips": [
                "🔄 Check back soon for new auctions",
                "⏰ Best opportunities often appear as auctions near their end",
            ],
            "generated_at": datetime.utcnow().isoformat(),
        }
    
    opportunities = []
    
    for auction in auctions:
        hours_left = (auction.end_time - datetime.utcnow()).total_seconds() / 3600
        
        # Skip auctions that have ended or are too far out
        if hours_left <= 0 or hours_left > 72:
            continue
        
        # Time urgency: Higher score for auctions ending soon
        if hours_left < 1:
            time_score = 5.0
            urgency = "Ending in minutes!"
        elif hours_left < 4:
            time_score = 3.0
            urgency = "Ending very soon"
        elif hours_left < 12:
            time_score = 2.0
            urgency = "Ending today"
        elif hours_left < 24:
            time_score = 1.5
            urgency = "Ending tomorrow"
        else:
            time_score = 1.0
            urgency = "Active"
        
        # Competition factor: Lower bids = better opportunity
        if auction.num_bids < 3:
            competition_score = 3.0
            competition = "Almost no competition"
        elif auction.num_bids < 10:
            competition_score = 2.0
            competition = "Low competition"
        elif auction.num_bids < 20:
            competition_score = 1.2
            competition = "Moderate competition"
        else:
            competition_score = 0.8
            competition = "High competition"
        
        # Price factor: Reasonable price points are opportunities
        if auction.current_bid < 100:
            price_score = 2.0
            price_range = "Budget-friendly"
        elif auction.current_bid < 500:
            price_score = 1.5
            price_range = "Mid-range"
        elif auction.current_bid < 2000:
            price_score = 1.2
            price_range = "Premium"
        else:
            price_score = 1.0
            price_range = "High-value"
        
        # Calculate overall opportunity score
        opportunity_score = round(time_score * competition_score * price_score, 1)
        
        # Only include if score is interesting (> 3)
        if opportunity_score < 3:
            continue
        
        listing = await _convert_to_listing(auction, db, include_valuation=False, now=now)
        
        recommendation = (
            "🔥 Hot" if opportunity_score >= 10 else
            "⚡ Great" if opportunity_score >= 6 else
            "👀 Watch"
        )
        
        opportunities.append({
            "auction": listing.model_dump(),
            "analysis": {
                "opportunity_score": opportunity_score,
                "time_score": time_score,
                "competition_score": competition_score,
                "price_score": price_score,
                "urgency": urgency,
                "competition": competition,
                "price_range": price_range,
                "recommendation": recommendation,
                "reasoning": f"{urgency} • {competition} • {price_range}",
            }
        })
    
    # Sort by opportunity score
    opportunities.sort(key=lambda x: x["analysis"]["opportunity_score"], reverse=True)
    
    return {
        "opportunities": opportunities[:15],
        "strategy_tips": [
            "⏰ Auctions ending soon have snipe potential",
            "📉 Low bid count = overlooked opportunities",
            "💡 Set a max budget and stick to it",
        ],
        "generated_at": datetime.utcnow().isoformat(),
    }


def _get_opportunity_reasoning(value_ratio: float, hours_left: float, num_bids: int, score: float) -> str:
    """Generate human-readable reasoning for the opportunity."""
    reasons = []
    
    if value_ratio > 2:
        reasons.append(f"Significantly undervalued ({value_ratio:.1f}× estimated value)")
    elif value_ratio > 1:
        reasons.append(f"Undervalued ({value_ratio:.1f}× estimated value)")
    else:
        reasons.append(f"Current bid exceeds our estimate ({value_ratio:.2f}×)")
    
    if hours_left < 1:
        reasons.append("⚡ Ending very soon - final chance to bid")
    elif hours_left < 4:
        reasons.append("⏰ Ending soon - limited time remaining")
    
    if num_bids < 5:
        reasons.append("📉 Very low competition - potential overlooked opportunity")
    elif num_bids < 10:
        reasons.append("📊 Moderate competition")
    else:
        reasons.append(f"🔥 High demand ({num_bids} bids)")
    
    return " | ".join(reasons)


def _calculate_pounce_score_v2(domain: str, tld: str, num_bids: int = 0, age_years: int = 0, is_pounce: bool = False) -> int:
    # Backward-compatible wrapper (shared implementation lives in services)
    from app.services.pounce_score import calculate_pounce_score_v2

    return calculate_pounce_score_v2(
        domain,
        tld,
        num_bids=num_bids,
        age_years=age_years,
        is_pounce=is_pounce,
    )


def _is_premium_domain(domain_name: str) -> bool:
    """Check if a domain looks premium/professional (Vanity Filter)."""
    parts = domain_name.rsplit('.', 1)
    name = parts[0] if parts else domain_name
    tld = parts[1].lower() if len(parts) > 1 else ""
    
    # Premium TLDs only
    premium_tlds = ['com', 'io', 'ai', 'co', 'de', 'ch', 'net', 'org', 'app', 'dev', 'xyz']
    if tld and tld not in premium_tlds:
        return False
    
    # Length check
    if len(name) > 15:
        return False
    if len(name) < 3:
        return False
    
    # Hyphen check
    if name.count('-') > 1:
        return False
    
    # Digit check
    if sum(1 for c in name if c.isdigit()) > 2:
        return False
    
    # Consonant cluster check
    consonants = 'bcdfghjklmnpqrstvwxyz'
    max_streak = 0
    current_streak = 0
    for c in name.lower():
        if c in consonants:
            current_streak += 1
            max_streak = max(max_streak, current_streak)
        else:
            current_streak = 0
    if max_streak > 4:
        return False
    
    return True


# ============== UNIFIED MARKET FEED ==============

@router.get("/feed", response_model=MarketFeedResponse)
async def get_market_feed(
    # Source filter
    source: str = Query("all", enum=["all", "pounce", "external"]),
    
    # Search & filters
    keyword: Optional[str] = Query(None, description="Search in domain names"),
    tld: Optional[str] = Query(None, description="Filter by TLD"),
    min_price: Optional[float] = Query(None, ge=0),
    max_price: Optional[float] = Query(None, ge=0),
    min_score: int = Query(0, ge=0, le=100),
    ending_within: Optional[int] = Query(None, description="Auctions ending within X hours"),
    verified_only: bool = Query(False, description="Only show verified Pounce listings"),
    
    # Sort
    sort_by: str = Query("score", enum=["score", "price_asc", "price_desc", "time", "newest"]),
    
    # Pagination
    limit: int = Query(50, le=200),
    offset: int = Query(0, ge=0),
    
    # Auth
    current_user: Optional[User] = Depends(get_current_user_optional),
    db: AsyncSession = Depends(get_db),
):
    """
    🚀 UNIFIED MARKET FEED — The heart of Pounce
    
    Combines:
    - 💎 Pounce Direct: DNS-verified user listings (instant buy)
    - 🏢 External Auctions: Scraped from GoDaddy, Sedo, NameJet, etc.
    
    For non-authenticated users:
    - Vanity filter applied (premium domains only)
    - Pounce Score visible but limited details
    
    For authenticated users (Trader/Tycoon):
    - Full access to all domains
    - Advanced filtering
    - Valuation data
    
    POUNCE EXCLUSIVE domains are highlighted and appear first.
    """
    # NOTE: This endpoint is called frequently by the Market UI.
    # Avoid loading *all* auctions/listings into Python. Instead, we:
    # - Apply filters + ordering in SQL where possible
    # - Over-fetch a bounded window for combined feeds ("all") and score-sorting
    now = datetime.utcnow()
    tld_clean = tld.lower().lstrip(".") if tld else None

    requested = offset + limit
    fetch_window = min(max(requested * 3, 200), 2000)  # bounded overfetch for merge/sort

    built: list[dict] = []  # {"item": MarketFeedItem, "newest_ts": datetime}

    # -----------------------------
    # Build base filters (SQL-side)
    # -----------------------------
    listing_filters = [DomainListing.status == ListingStatus.ACTIVE.value]
    if keyword:
        listing_filters.append(DomainListing.domain.ilike(f"%{keyword}%"))
    if verified_only:
        listing_filters.append(DomainListing.verification_status == VerificationStatus.VERIFIED.value)
    if min_price is not None:
        listing_filters.append(DomainListing.asking_price >= min_price)
    if max_price is not None:
        listing_filters.append(DomainListing.asking_price <= max_price)
    if tld_clean:
        listing_filters.append(DomainListing.domain.ilike(f"%.{tld_clean}"))

    auction_filters = [
        DomainAuction.is_active == True,
        DomainAuction.end_time > now,
    ]
    if keyword:
        auction_filters.append(DomainAuction.domain.ilike(f"%{keyword}%"))
    if tld_clean:
        auction_filters.append(DomainAuction.tld == tld_clean)
    if min_price is not None:
        auction_filters.append(DomainAuction.current_bid >= min_price)
    if max_price is not None:
        auction_filters.append(DomainAuction.current_bid <= max_price)
    if ending_within:
        cutoff = now + timedelta(hours=ending_within)
        auction_filters.append(DomainAuction.end_time <= cutoff)

    # -----------------------------
    # Counts (used for UI stats)
    # -----------------------------
    pounce_total = 0
    auction_total = 0
    if source in ["all", "pounce"]:
        pounce_total = (await db.execute(select(func.count(DomainListing.id)).where(and_(*listing_filters)))).scalar() or 0
    if source in ["all", "external"]:
        auction_total = (await db.execute(select(func.count(DomainAuction.id)).where(and_(*auction_filters)))).scalar() or 0

    # -----------------------------
    # Fetch + build items (bounded)
    # -----------------------------
    # For "all": fetch a bounded window from each source and then merge/sort in Python.
    # For single-source: fetch offset/limit directly when sort can be pushed to SQL.
    listing_offset = 0
    listing_limit = fetch_window
    auction_offset = 0
    auction_limit = fetch_window

    if source == "pounce":
        listing_offset = offset
        listing_limit = limit
    if source == "external":
        auction_offset = offset
        auction_limit = limit

    # Pounce Direct listings
    if source in ["all", "pounce"]:
        listing_query = select(DomainListing).where(and_(*listing_filters))

        # SQL ordering for listings (best-effort)
        if sort_by == "price_asc":
            listing_query = listing_query.order_by(func.coalesce(DomainListing.asking_price, 0).asc())
        elif sort_by == "price_desc":
            listing_query = listing_query.order_by(func.coalesce(DomainListing.asking_price, 0).desc())
        elif sort_by == "newest":
            listing_query = listing_query.order_by(DomainListing.updated_at.desc())
        else:
            # score/time: prefer higher score first for listings
            listing_query = listing_query.order_by(DomainListing.pounce_score.desc(), DomainListing.updated_at.desc())

        listing_query = listing_query.offset(listing_offset).limit(listing_limit)
        listings = (await db.execute(listing_query)).scalars().all()

        for listing in listings:
            domain_tld = listing.domain.rsplit(".", 1)[1] if "." in listing.domain else ""
            pounce_score = listing.pounce_score or _calculate_pounce_score_v2(listing.domain, domain_tld, is_pounce=True)
            if pounce_score < min_score:
                continue
            
            item = MarketFeedItem(
                id=f"pounce-{listing.id}",
                domain=listing.domain,
                tld=domain_tld,
                price=listing.asking_price or 0,
                currency=listing.currency or "USD",
                price_type="fixed" if listing.price_type == "fixed" else "negotiable",
                status="instant",
                source="Pounce",
                is_pounce=True,
                verified=listing.is_verified,
                seller_verified=listing.is_verified,
                slug=listing.slug,
                url=f"/buy/{listing.slug}",
                is_external=False,
                pounce_score=pounce_score,
            )
            built.append({"item": item, "newest_ts": listing.updated_at or listing.created_at or datetime.min})
    
    # External auctions (from DB)
    if source in ["all", "external"]:
        auction_query = select(DomainAuction).where(and_(*auction_filters))

        # SQL ordering for auctions when possible
        if sort_by == "time":
            auction_query = auction_query.order_by(DomainAuction.end_time.asc())
        elif sort_by == "price_asc":
            auction_query = auction_query.order_by(DomainAuction.current_bid.asc())
        elif sort_by == "price_desc":
            auction_query = auction_query.order_by(DomainAuction.current_bid.desc())
        elif sort_by == "newest":
            auction_query = auction_query.order_by(DomainAuction.updated_at.desc())
        else:
            # score: prefer persisted score for DB-level sorting
            auction_query = auction_query.order_by(
                func.coalesce(DomainAuction.pounce_score, 0).desc(),
                DomainAuction.updated_at.desc(),
            )

        auction_query = auction_query.offset(auction_offset).limit(auction_limit)
        auctions = (await db.execute(auction_query)).scalars().all()
        
        for auction in auctions:
            # Vanity filter for anonymous users
            if current_user is None and not _is_premium_domain(auction.domain):
                continue
            
            pounce_score = auction.pounce_score
            if pounce_score is None:
                pounce_score = _calculate_pounce_score_v2(
                    auction.domain,
                    auction.tld,
                    num_bids=auction.num_bids,
                    age_years=auction.age_years or 0,
                    is_pounce=False,
                )
            if pounce_score < min_score:
                continue
            
            item = MarketFeedItem(
                id=f"auction-{auction.id}",
                domain=auction.domain,
                tld=auction.tld,
                price=auction.current_bid,
                currency=auction.currency,
                price_type="bid",
                status="auction",
                source=auction.platform,
                is_pounce=False,
                verified=False,
                time_remaining=_format_time_remaining(auction.end_time, now=now),
                end_time=auction.end_time,
                num_bids=auction.num_bids,
                url=_get_affiliate_url(auction.platform, auction.domain, auction.auction_url),
                is_external=True,
                pounce_score=pounce_score,
            )
            built.append({"item": item, "newest_ts": auction.updated_at or auction.scraped_at or datetime.min})
        
        # =========================================================================
        # LIVE SEDO DATA - Fetch and merge real-time listings from Sedo API
        # =========================================================================
        try:
            from app.services.sedo_api import sedo_client
            
            if sedo_client.is_configured:
                # Use search keyword or fall back to popular terms for discovery
                sedo_keyword = keyword
                if not sedo_keyword:
                    # Fetch popular domains when no specific search
                    import random
                    popular_terms = ["ai", "tech", "crypto", "app", "cloud", "digital", "smart", "pro"]
                    sedo_keyword = random.choice(popular_terms)
                
                # Fetch live Sedo listings (limit to avoid slow responses)
                sedo_listings = await sedo_client.get_listings_for_display(
                    keyword=sedo_keyword,
                    tld=tld_clean,
                    page_size=min(30, limit)  # Cap at 30 to avoid slow API calls
                )
                
                # Track domains already in results to avoid duplicates
                existing_domains = {item["item"].domain.lower() for item in built}
                
                for sedo_item in sedo_listings:
                    domain = sedo_item.get("domain", "").lower()
                    
                    # Skip if already have this domain from scraped data
                    if domain in existing_domains:
                        continue
                    
                    # Apply vanity filter for anonymous users
                    if current_user is None and not _is_premium_domain(domain):
                        continue
                    
                    # Apply price filters
                    price = sedo_item.get("price", 0)
                    if min_price is not None and price < min_price and price > 0:
                        continue
                    if max_price is not None and price > max_price:
                        continue
                    
                    domain_tld = sedo_item.get("tld", "")
                    pounce_score = _calculate_pounce_score_v2(
                        domain,
                        domain_tld,
                        num_bids=0,
                        age_years=0,
                        is_pounce=False,
                    )
                    
                    if pounce_score < min_score:
                        continue
                    
                    # Determine price type
                    price_type = "bid" if sedo_item.get("is_auction") else (
                        "negotiable" if price == 0 else "fixed"
                    )
                    
                    item = MarketFeedItem(
                        id=f"sedo-live-{hash(domain) % 1000000}",
                        domain=domain,
                        tld=domain_tld,
                        price=price,
                        currency="USD",
                        price_type=price_type,
                        status="auction" if sedo_item.get("is_auction") else "instant",
                        source="Sedo",
                        is_pounce=False,
                        verified=False,
                        time_remaining=None,
                        end_time=None,
                        num_bids=None,
                        url=sedo_item.get("url", ""),
                        is_external=True,
                        pounce_score=pounce_score,
                    )
                    built.append({"item": item, "newest_ts": now})
                    existing_domains.add(domain)
                    
                    # Update auction count
                    auction_total += 1
                    
        except Exception as e:
            logger.warning(f"Failed to fetch live Sedo data: {e}")
    
    # -----------------------------
    # Merge sort (Python) + paginate
    # -----------------------------
    if sort_by == "score":
        built.sort(key=lambda x: (x["item"].pounce_score, int(x["item"].is_pounce), x["item"].domain), reverse=True)
    elif sort_by == "price_asc":
        built.sort(key=lambda x: (x["item"].price, -int(x["item"].is_pounce), x["item"].domain))
    elif sort_by == "price_desc":
        built.sort(key=lambda x: (-x["item"].price, -int(x["item"].is_pounce), x["item"].domain))
    elif sort_by == "time":
        # Pounce Direct first (no time limit), then by end time
        built.sort(
            key=lambda x: (0 if x["item"].is_pounce else 1, x["item"].end_time or datetime.max)
        )
    elif sort_by == "newest":
        built.sort(key=lambda x: (int(x["item"].is_pounce), x["newest_ts"]), reverse=True)
    
    total = pounce_total + auction_total if source == "all" else (pounce_total if source == "pounce" else auction_total)
    
    page_slice = built[offset:offset + limit]
    items = [x["item"] for x in page_slice]
    
    # Unique sources (after pagination)
    sources = list(set(item.source for item in items))
    
    # Last update time (auctions)
    if source in ["all", "external"]:
        last_update_result = await db.execute(select(func.max(DomainAuction.updated_at)))
        last_updated = last_update_result.scalar() or now
    else:
        last_updated = now
    
    return MarketFeedResponse(
        items=items,
        total=total,
        pounce_direct_count=pounce_total,
        auction_count=auction_total,
        sources=sources,
        last_updated=last_updated,
        filters_applied={
            "source": source,
            "keyword": keyword,
            "tld": tld,
            "min_price": min_price,
            "max_price": max_price,
            "min_score": min_score,
            "ending_within": ending_within,
            "verified_only": verified_only,
            "sort_by": sort_by,
        },
    )
