"""Stale coverage detection — marks techniques whose last validated test is older than a configurable threshold.

This is the simple version.  The full Decay Engine (Fase 8) will replace
this with a multi-factor, configurable decay model with confidence scores.
"""

# Import logging
import logging

# Import datetime, timedelta, timezone from datetime
from datetime import datetime, timedelta, timezone

# Import func from sqlalchemy
from sqlalchemy import func

# Import Session from sqlalchemy.orm
from sqlalchemy.orm import Session

# Import settings from app.config
from app.config import settings

# Import TechniqueStatus, TestState from app.models.enums
from app.models.enums import TechniqueStatus, TestState

# Import Technique from app.models.technique
from app.models.technique import Technique

# Import Test from app.models.test
from app.models.test import Test

# Assign logger = logging.getLogger(__name__)
logger = logging.getLogger(__name__)

# Assign STALE_THRESHOLD_DAYS = settings.STALE_THRESHOLD_DAYS
STALE_THRESHOLD_DAYS = settings.STALE_THRESHOLD_DAYS


# Define function detect_stale_coverage
def detect_stale_coverage(db: Session) -> int:
    """Scan all techniques and flag those with stale coverage.

    A technique is considered stale when:
    - It has a status other than ``not_evaluated``, AND
    - Its most recent *validated* test is older than *STALE_THRESHOLD_DAYS*, OR
    - It has never had a validated test (but has been manually marked as
      covered/partial).

    Args:
        db (Session): Active SQLAlchemy database session.

    Returns:
        int: Number of techniques newly flagged as stale (``review_required``
            set to ``True``) in this run.
    """
    # Assign cutoff = datetime.now(timezone.utc) - timedelta(days=STALE_THRESHOLD_DAYS)
    cutoff = datetime.now(timezone.utc) - timedelta(days=STALE_THRESHOLD_DAYS)

    # Assign last_validated = func.coalesce(
    last_validated = func.coalesce(
        Test.blue_validated_at,
        Test.red_validated_at,
        Test.created_at,
    )

    # Subquery: latest validated test date per technique
    latest_test = (
        db.query(
            Test.technique_id,
            func.max(last_validated).label("last_tested"),
        )
        # Chain .filter() call
        .filter(Test.state == TestState.validated)
        # Chain .group_by() call
        .group_by(Test.technique_id)
        # Chain .subquery() call
        .subquery()
    )

    # Find techniques that are stale
    stale_techniques = (
        db.query(Technique)
        # Chain .outerjoin() call
        .outerjoin(latest_test, Technique.id == latest_test.c.technique_id)
        # Chain .filter() call
        .filter(
            # Either tested before cutoff, or never tested at all
            (latest_test.c.last_tested < cutoff)
            | (latest_test.c.last_tested.is_(None))
        )
        # Chain .filter() call
        .filter(
            # Only flag techniques that have a real status (not never-evaluated ones)
            Technique.status_global != TechniqueStatus.not_evaluated
        )
        # Chain .all() call
        .all()
    )

    # Assign count = 0
    count = 0
    # Iterate over stale_techniques
    for tech in stale_techniques:
        # Check: not tech.review_required
        if not tech.review_required:
            # Assign tech.review_required = True
            tech.review_required = True
            # Assign count = 1
            count += 1
            # Log info: "Marked %s as stale coverage", tech.mitre_id
            logger.info("Marked %s as stale coverage", tech.mitre_id)

    # Check: count > 0
    if count > 0:
        # Commit all pending changes to the database
        db.commit()
        # Log info:
        logger.info(
            # Literal argument value
            "Stale coverage detection complete — %d techniques flagged", count
        )
    # Fallback: handle remaining cases
    else:
        # Log info: "Stale coverage detection complete — no new stale
        logger.info("Stale coverage detection complete — no new stale techniques")

    # Return count
    return count
