"""SQL query functions for the admin SEO hub views.

All heavy lifting happens in SQL. Functions accept filter parameters
and return plain dicts/lists.
"""

from datetime import timedelta

from ..core import execute, fetch_all, fetch_one, utcnow


def _date_cutoff(date_range_days: int) -> str:
    """Return ISO date string for N days ago."""
    return (utcnow() - timedelta(days=date_range_days)).strftime("%Y-%m-%d")


async def get_search_performance(
    date_range_days: int = 28,
    source: str | None = None,
) -> dict:
    """Aggregate search performance: total clicks, impressions, avg CTR, avg position."""
    assert 1 <= date_range_days <= 730

    cutoff = _date_cutoff(date_range_days)
    source_filter = "AND source = ?" if source else ""
    params = [cutoff]
    if source:
        params.append(source)

    row = await fetch_one(
        f"""SELECT
                COALESCE(SUM(clicks), 0)      AS total_clicks,
                COALESCE(SUM(impressions), 0)  AS total_impressions,
                CASE WHEN SUM(impressions) > 0
                     THEN CAST(SUM(clicks) AS REAL) / SUM(impressions)
                     ELSE 0 END                AS avg_ctr,
                CASE WHEN SUM(impressions) > 0
                     THEN SUM(position_avg * impressions) / SUM(impressions)
                     ELSE 0 END                AS avg_position
            FROM seo_search_metrics
            WHERE metric_date >= ? {source_filter}""",
        tuple(params),
    )
    return dict(row) if row else {
        "total_clicks": 0, "total_impressions": 0,
        "avg_ctr": 0, "avg_position": 0,
    }


async def get_top_queries(
    date_range_days: int = 28,
    source: str | None = None,
    limit: int = 50,
) -> list[dict]:
    """Top queries by impressions with clicks, CTR, avg position."""
    assert 1 <= date_range_days <= 730
    assert 1 <= limit <= 500

    cutoff = _date_cutoff(date_range_days)
    source_filter = "AND source = ?" if source else ""
    params: list = [cutoff]
    if source:
        params.append(source)
    params.append(limit)

    rows = await fetch_all(
        f"""SELECT
                query,
                SUM(clicks)      AS clicks,
                SUM(impressions) AS impressions,
                CASE WHEN SUM(impressions) > 0
                     THEN CAST(SUM(clicks) AS REAL) / SUM(impressions)
                     ELSE 0 END  AS ctr,
                CASE WHEN SUM(impressions) > 0
                     THEN SUM(position_avg * impressions) / SUM(impressions)
                     ELSE 0 END  AS position_avg
            FROM seo_search_metrics
            WHERE metric_date >= ?
              AND query IS NOT NULL AND query != ''
              {source_filter}
            GROUP BY query
            ORDER BY impressions DESC
            LIMIT ?""",
        tuple(params),
    )
    return [dict(r) for r in rows]


async def get_top_pages(
    date_range_days: int = 28,
    source: str | None = None,
    limit: int = 50,
) -> list[dict]:
    """Top pages by impressions with clicks, CTR, avg position."""
    assert 1 <= date_range_days <= 730
    assert 1 <= limit <= 500

    cutoff = _date_cutoff(date_range_days)
    source_filter = "AND source = ?" if source else ""
    params: list = [cutoff]
    if source:
        params.append(source)
    params.append(limit)

    rows = await fetch_all(
        f"""SELECT
                page_url,
                SUM(clicks)      AS clicks,
                SUM(impressions) AS impressions,
                CASE WHEN SUM(impressions) > 0
                     THEN CAST(SUM(clicks) AS REAL) / SUM(impressions)
                     ELSE 0 END  AS ctr,
                CASE WHEN SUM(impressions) > 0
                     THEN SUM(position_avg * impressions) / SUM(impressions)
                     ELSE 0 END  AS position_avg
            FROM seo_search_metrics
            WHERE metric_date >= ?
              {source_filter}
            GROUP BY page_url
            ORDER BY impressions DESC
            LIMIT ?""",
        tuple(params),
    )
    return [dict(r) for r in rows]


async def get_country_breakdown(
    date_range_days: int = 28,
) -> list[dict]:
    """Clicks and impressions by country."""
    assert 1 <= date_range_days <= 730

    cutoff = _date_cutoff(date_range_days)
    rows = await fetch_all(
        """SELECT
               country,
               SUM(clicks)      AS clicks,
               SUM(impressions) AS impressions
           FROM seo_search_metrics
           WHERE metric_date >= ?
             AND country IS NOT NULL AND country != ''
           GROUP BY country
           ORDER BY impressions DESC
           LIMIT 50""",
        (cutoff,),
    )
    return [dict(r) for r in rows]


async def get_device_breakdown(
    date_range_days: int = 28,
) -> list[dict]:
    """Clicks and impressions by device type (GSC only)."""
    assert 1 <= date_range_days <= 730

    cutoff = _date_cutoff(date_range_days)
    rows = await fetch_all(
        """SELECT
               device,
               SUM(clicks)      AS clicks,
               SUM(impressions) AS impressions
           FROM seo_search_metrics
           WHERE metric_date >= ?
             AND source = 'gsc'
             AND device IS NOT NULL AND device != ''
           GROUP BY device
           ORDER BY impressions DESC""",
        (cutoff,),
    )
    return [dict(r) for r in rows]


async def get_funnel_metrics(
    date_range_days: int = 28,
) -> dict:
    """Full funnel: search → analytics → conversions.

    Combines search metrics (GSC/Bing), analytics (Umami), and
    business metrics (planner users, leads) from SQLite.
    """
    assert 1 <= date_range_days <= 730

    cutoff = _date_cutoff(date_range_days)

    # Search layer
    search = await fetch_one(
        """SELECT
               COALESCE(SUM(impressions), 0) AS impressions,
               COALESCE(SUM(clicks), 0)      AS clicks
           FROM seo_search_metrics
           WHERE metric_date >= ?""",
        (cutoff,),
    )

    # Analytics layer
    analytics = await fetch_one(
        """SELECT
               COALESCE(SUM(pageviews), 0) AS pageviews,
               COALESCE(SUM(visitors), 0)  AS visitors
           FROM seo_analytics_metrics
           WHERE metric_date >= ?
             AND page_url != '/'""",
        (cutoff,),
    )

    # Business layer (from existing SQLite tables)
    planner_users = await fetch_one(
        """SELECT COUNT(DISTINCT user_id) AS cnt
           FROM scenarios
           WHERE deleted_at IS NULL
             AND created_at >= ?""",
        (cutoff,),
    )

    leads = await fetch_one(
        """SELECT COUNT(*) AS cnt
           FROM lead_requests
           WHERE lead_type = 'quote'
             AND created_at >= ?""",
        (cutoff,),
    )

    imp = search["impressions"] if search else 0
    clicks = search["clicks"] if search else 0
    pvs = analytics["pageviews"] if analytics else 0
    vis = analytics["visitors"] if analytics else 0
    planners = planner_users["cnt"] if planner_users else 0
    lead_count = leads["cnt"] if leads else 0

    return {
        "impressions": imp,
        "clicks": clicks,
        "pageviews": pvs,
        "visitors": vis,
        "planner_users": planners,
        "leads": lead_count,
        # Conversion rates between stages
        "ctr": clicks / imp if imp > 0 else 0,
        "click_to_view": pvs / clicks if clicks > 0 else 0,
        "view_to_visitor": vis / pvs if pvs > 0 else 0,
        "visitor_to_planner": planners / vis if vis > 0 else 0,
        "planner_to_lead": lead_count / planners if planners > 0 else 0,
    }


async def get_article_scorecard(
    date_range_days: int = 28,
    template_slug: str | None = None,
    country: str | None = None,
    language: str | None = None,
    sort_by: str = "impressions",
    sort_dir: str = "desc",
    limit: int = 100,
) -> list[dict]:
    """Per-article scorecard joining articles + search + analytics metrics.

    Returns article metadata enriched with search and analytics data,
    plus attention flags for articles needing action.
    """
    assert 1 <= date_range_days <= 730
    assert 1 <= limit <= 500
    assert sort_dir in ("asc", "desc")

    # Allowlist sort columns to prevent SQL injection
    sort_columns = {
        "impressions", "clicks", "ctr", "position_avg",
        "pageviews", "title", "published_at",
    }
    if sort_by not in sort_columns:
        sort_by = "impressions"

    cutoff = _date_cutoff(date_range_days)

    wheres = ["a.status = 'published'"]
    params: list = [cutoff, cutoff]

    if template_slug:
        wheres.append("a.template_slug = ?")
        params.append(template_slug)
    if country:
        wheres.append("a.country = ?")
        params.append(country)
    if language:
        wheres.append("a.language = ?")
        params.append(language)

    where_clause = " AND ".join(wheres)
    params.append(limit)

    rows = await fetch_all(
        f"""SELECT
                a.id,
                a.title,
                a.url_path,
                a.template_slug,
                a.country,
                a.language,
                a.published_at,
                COALESCE(s.impressions, 0)  AS impressions,
                COALESCE(s.clicks, 0)       AS clicks,
                COALESCE(s.ctr, 0)          AS ctr,
                COALESCE(s.position_avg, 0) AS position_avg,
                COALESCE(u.pageviews, 0)    AS pageviews,
                COALESCE(u.visitors, 0)     AS visitors,
                u.bounce_rate,
                u.time_avg_seconds,
                -- Attention flags
                CASE WHEN COALESCE(s.impressions, 0) > 100
                      AND COALESCE(s.ctr, 0) < 0.02
                     THEN 1 ELSE 0 END AS flag_low_ctr,
                CASE WHEN COALESCE(s.clicks, 0) = 0
                      AND a.published_at <= date('now', '-30 days')
                     THEN 1 ELSE 0 END AS flag_no_clicks
            FROM articles a
            LEFT JOIN (
                SELECT page_url,
                       SUM(impressions) AS impressions,
                       SUM(clicks) AS clicks,
                       CASE WHEN SUM(impressions) > 0
                            THEN CAST(SUM(clicks) AS REAL) / SUM(impressions)
                            ELSE 0 END AS ctr,
                       CASE WHEN SUM(impressions) > 0
                            THEN SUM(position_avg * impressions) / SUM(impressions)
                            ELSE 0 END AS position_avg
                FROM seo_search_metrics
                WHERE metric_date >= ?
                GROUP BY page_url
            ) s ON s.page_url = a.url_path
            LEFT JOIN (
                SELECT page_url,
                       SUM(pageviews) AS pageviews,
                       SUM(visitors) AS visitors,
                       AVG(bounce_rate) AS bounce_rate,
                       AVG(time_avg_seconds) AS time_avg_seconds
                FROM seo_analytics_metrics
                WHERE metric_date >= ?
                GROUP BY page_url
            ) u ON u.page_url = a.url_path
            WHERE {where_clause}
            ORDER BY {sort_by} {sort_dir}
            LIMIT ?""",
        tuple(params),
    )
    return [dict(r) for r in rows]


async def get_sync_status() -> list[dict]:
    """Last sync status for each source (gsc, bing, umami)."""
    rows = await fetch_all(
        """SELECT source, status, rows_synced, error,
                  started_at, completed_at, duration_ms
           FROM seo_sync_log
           WHERE id IN (
               SELECT MAX(id) FROM seo_sync_log GROUP BY source
           )
           ORDER BY source"""
    )
    return [dict(r) for r in rows]


async def cleanup_old_metrics(retention_days: int = 365) -> int:
    """Delete metrics older than retention_days. Returns rows deleted."""
    assert 30 <= retention_days <= 1095

    cutoff = _date_cutoff(retention_days)

    deleted_search = await execute(
        "DELETE FROM seo_search_metrics WHERE metric_date < ?", (cutoff,)
    )
    deleted_analytics = await execute(
        "DELETE FROM seo_analytics_metrics WHERE metric_date < ?", (cutoff,)
    )
    # Sync log: keep 30 days
    sync_cutoff = _date_cutoff(30)
    deleted_sync = await execute(
        "DELETE FROM seo_sync_log WHERE started_at < ?", (sync_cutoff,)
    )

    return (deleted_search or 0) + (deleted_analytics or 0) + (deleted_sync or 0)
