"""Campaign scheduler service — recurring campaign execution.

Handles checking which recurring campaigns are due, cloning them with
fresh tests, and computing the next run date.
"""

# Import logging
import logging

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

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

# Import Campaign, CampaignTest from app.models.campaign
from app.models.campaign import Campaign, CampaignTest

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

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

# Import User from app.models.user
from app.models.user import User

# Import log_action from app.services.audit_service
from app.services.audit_service import log_action

# Import create_notification from app.services.notification_service
from app.services.notification_service import create_notification

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


# ---------------------------------------------------------------------------
# Next-run calculation
# ---------------------------------------------------------------------------


def calculate_next_run(current_date: datetime, pattern: str) -> datetime:
    """Compute the next run date from *current_date* and a recurrence pattern.

    Supported patterns:
    - ``weekly``  : +7 days
    - ``monthly`` : +30 days
    - ``quarterly``: +90 days
    """
    # Assign offsets = {
    offsets = {
        # Literal argument value
        "weekly": timedelta(days=7),
        # Literal argument value
        "monthly": timedelta(days=30),
        # Literal argument value
        "quarterly": timedelta(days=90),
    }
    # Return current_date + offsets.get(pattern, timedelta(days=30))
    return current_date + offsets.get(pattern, timedelta(days=30))


# ---------------------------------------------------------------------------
# Clone a campaign
# ---------------------------------------------------------------------------


def _clone_campaign(db: Session, original: Campaign) -> Campaign:
    """Create a new child campaign from a recurring template.

    1. Clone the campaign with a date-stamped name.
    2. For each ``CampaignTest`` in the original, create a new ``Test``
       with the same base data (in ``draft`` state) and link it.
    3. Activate the new campaign.
    """
    # Assign now = datetime.utcnow()
    now = datetime.utcnow()
    # Assign run_label = now.strftime("%Y-%m-%d")
    run_label = now.strftime("%Y-%m-%d")

    # Assign child = Campaign(
    child = Campaign(
        # Keyword argument: name
        name=f"{original.name} (Run {run_label})",
        # Keyword argument: description
        description=original.description,
        # Keyword argument: type
        type=original.type,
        # Keyword argument: threat_actor_id
        threat_actor_id=original.threat_actor_id,
        # Keyword argument: status
        status="active",
        # Keyword argument: created_by
        created_by=original.created_by,
        # Keyword argument: target_platform
        target_platform=original.target_platform,
        # Keyword argument: tags
        tags=original.tags or [],
        # Keyword argument: parent_campaign_id
        parent_campaign_id=original.id,
    )
    # Stage new record(s) for database insertion
    db.add(child)
    # Flush changes to DB without committing the transaction
    db.flush()  # get child.id

    # Clone each campaign_test with a fresh Test
    original_cts = (
        db.query(CampaignTest)
        # Chain .filter() call
        .filter(CampaignTest.campaign_id == original.id)
        # Chain .order_by() call
        .order_by(CampaignTest.order_index)
        # Chain .all() call
        .all()
    )

    # Iterate over original_cts
    for ct in original_cts:
        # Assign src_test = ct.test
        src_test = ct.test
        # Check: not src_test
        if not src_test:
            # Skip to the next loop iteration
            continue

        # Assign new_test = Test(
        new_test = Test(
            # Keyword argument: technique_id
            technique_id=src_test.technique_id,
            # Keyword argument: name
            name=src_test.name,
            # Keyword argument: description
            description=src_test.description,
            # Keyword argument: platform
            platform=src_test.platform,
            # Keyword argument: procedure_text
            procedure_text=src_test.procedure_text,
            # Keyword argument: tool_used
            tool_used=src_test.tool_used,
            # Keyword argument: created_by
            created_by=original.created_by,
            # Keyword argument: state
            state=TestState.draft,
        )
        # Stage new record(s) for database insertion
        db.add(new_test)
        # Flush changes to DB without committing the transaction
        db.flush()  # get new_test.id

        # Assign new_ct = CampaignTest(
        new_ct = CampaignTest(
            # Keyword argument: campaign_id
            campaign_id=child.id,
            # Keyword argument: test_id
            test_id=new_test.id,
            # Keyword argument: order_index
            order_index=ct.order_index,
            # Keyword argument: phase
            phase=ct.phase,
            # depends_on is not copied — would need ID remapping
        )
        # Stage new record(s) for database insertion
        db.add(new_ct)

    # Flush changes to DB without committing the transaction
    db.flush()
    # Return child
    return child


# ---------------------------------------------------------------------------
# Check and run recurring campaigns (daily job)
# ---------------------------------------------------------------------------


def check_and_run_recurring_campaigns(db: Session) -> int:
    """Check all recurring campaigns and clone any that are due.

    Returns the number of campaigns spawned.
    """
    # Assign now = datetime.utcnow()
    now = datetime.utcnow()

    # Assign due_campaigns = (
    due_campaigns = (
        db.query(Campaign)
        # Chain .filter() call
        .filter(
            Campaign.is_recurring == True,  # noqa: E712
            Campaign.next_run_at <= now,
        )
        # Chain .all() call
        .all()
    )

    # Assign spawned = 0
    spawned = 0

    # Iterate over due_campaigns
    for campaign in due_campaigns:
        # Attempt the following; catch errors below
        try:
            # Assign child = _clone_campaign(db, campaign)
            child = _clone_campaign(db, campaign)

            # Update the original's scheduling fields
            campaign.last_run_at = now
            # Assign campaign.next_run_at = calculate_next_run(now, campaign.recurrence_pattern or "monthly")
            campaign.next_run_at = calculate_next_run(now, campaign.recurrence_pattern or "monthly")

            # Commit all pending changes to the database
            db.commit()
            # Reload ORM object attributes from the database
            db.refresh(child)

            # Audit
            log_action(
                db,
                # Keyword argument: user_id
                user_id=campaign.created_by,
                # Keyword argument: action
                action="recurring_campaign_run",
                # Keyword argument: entity_type
                entity_type="campaign",
                # Keyword argument: entity_id
                entity_id=child.id,
                # Keyword argument: details
                details={
                    # Literal argument value
                    "parent_campaign_id": str(campaign.id),
                    # Literal argument value
                    "child_campaign_name": child.name,
                    # Literal argument value
                    "pattern": campaign.recurrence_pattern,
                },
            )
            # Commit all pending changes to the database
            db.commit()

            # Notify
            if campaign.created_by:
                # Call create_notification()
                create_notification(
                    db,
                    # Keyword argument: user_id
                    user_id=campaign.created_by,
                    # Keyword argument: type
                    type="recurring_campaign_run",
                    # Keyword argument: title
                    title="Recurring campaign executed",
                    # Keyword argument: message
                    message=(
                        f'Campaign "{child.name}" was automatically created '
                        f'from recurring template "{campaign.name}".'
                    ),
                    # Keyword argument: entity_type
                    entity_type="campaign",
                    # Keyword argument: entity_id
                    entity_id=child.id,
                )

            # Notify red_tech users
            red_techs = db.query(User).filter(User.role == "red_tech", User.is_active == True).all()  # noqa: E712
            # Iterate over red_techs
            for user in red_techs:
                # Call create_notification()
                create_notification(
                    db,
                    # Keyword argument: user_id
                    user_id=user.id,
                    # Keyword argument: type
                    type="campaign_activated",
                    # Keyword argument: title
                    title="New recurring campaign active",
                    # Keyword argument: message
                    message=f'Campaign "{child.name}" is now active and ready for execution.',
                    # Keyword argument: entity_type
                    entity_type="campaign",
                    # Keyword argument: entity_id
                    entity_id=child.id,
                )

            # Assign spawned = 1
            spawned += 1
            # Log info: "Spawned child campaign '%s' from parent '%s'", ch
            logger.info("Spawned child campaign '%s' from parent '%s'", child.name, campaign.name)

        # Handle Exception
        except Exception:
            # Roll back all uncommitted changes
            db.rollback()
            # Log exception: "Failed to run recurring campaign '%s'", campaign.
            logger.exception("Failed to run recurring campaign '%s'", campaign.name)

    # Return spawned
    return spawned
