"""
Crop plan endpoints — saved planning scenarios for a logged-in customer.

POST   /crop-plans          create a new plan
GET    /crop-plans          list the current user's plans
GET    /crop-plans/{id}     get one plan, with its latest analysis status
PATCH  /crop-plans/{id}     rename, change risk tolerance, or archive
DELETE /crop-plans/{id}     hard delete (only if no analyses reference it)

All endpoints are scoped to the logged-in user — a customer can only
ever see or modify their own plans, enforced by filtering every query
on CropPlan.user_id == current_user.id, never trusting a plan id alone.
"""

from datetime import datetime

from fastapi import APIRouter, Depends
from sqlalchemy import desc, select
from sqlalchemy.ext.asyncio import AsyncSession

from app.core.current_user import get_current_user
from app.core.exceptions import AppException
from app.core.logging_config import get_logger
from app.db.models import AnalysisRecord, CropPlan, User
from app.db.session import get_db_session
from app.schemas.crop_plan import CropPlanCreateRequest, CropPlanResponse, CropPlanUpdateRequest

router = APIRouter()
logger = get_logger(__name__)


class CropPlanNotFoundError(AppException):
    status_code = 404
    error_code = "CROP_PLAN_NOT_FOUND"


class CropPlanHasAnalysesError(AppException):
    status_code = 409
    error_code = "CROP_PLAN_HAS_ANALYSES"


async def _get_owned_plan(plan_id: str, user_id: str, db: AsyncSession) -> CropPlan:
    result = await db.execute(
        select(CropPlan).where(CropPlan.id == plan_id, CropPlan.user_id == user_id)
    )
    plan = result.scalar_one_or_none()
    if plan is None:
        raise CropPlanNotFoundError(message="Crop plan not found")
    return plan


async def _latest_analysis_for_plan(plan_id: str, db: AsyncSession) -> AnalysisRecord | None:
    result = await db.execute(
        select(AnalysisRecord)
        .where(AnalysisRecord.crop_plan_id == plan_id)
        .order_by(desc(AnalysisRecord.created_at))
        .limit(1)
    )
    return result.scalar_one_or_none()


def _to_response(plan: CropPlan, latest_analysis: AnalysisRecord | None = None) -> CropPlanResponse:
    return CropPlanResponse(
        id=plan.id,
        name=plan.name,
        crop=plan.crop,
        location=plan.location,
        planting_month=plan.planting_month,
        risk_tolerance=plan.risk_tolerance,
        is_archived=plan.is_archived,
        created_at=plan.created_at.isoformat(),
        updated_at=plan.updated_at.isoformat(),
        latest_feasibility_label=latest_analysis.feasibility_label if latest_analysis else None,
        latest_analysis_at=latest_analysis.created_at.isoformat() if latest_analysis else None,
    )


@router.post("/crop-plans", response_model=CropPlanResponse, status_code=201)
async def create_crop_plan(
    payload: CropPlanCreateRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db_session),
) -> CropPlanResponse:
    plan = CropPlan(
        user_id=current_user.id,
        name=payload.name,
        crop=payload.crop.strip().lower(),
        location=payload.location.strip().lower(),
        planting_month=payload.planting_month,
        risk_tolerance=payload.risk_tolerance,
    )
    db.add(plan)
    await db.commit()
    await db.refresh(plan)

    logger.info(f"Created crop plan {plan.id} for user_id={current_user.id}")
    return _to_response(plan)


@router.get("/crop-plans", response_model=list[CropPlanResponse])
async def list_crop_plans(
    include_archived: bool = False,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db_session),
) -> list[CropPlanResponse]:
    query = select(CropPlan).where(CropPlan.user_id == current_user.id)
    if not include_archived:
        query = query.where(CropPlan.is_archived.is_(False))
    query = query.order_by(desc(CropPlan.updated_at))

    result = await db.execute(query)
    plans = result.scalars().all()

    responses = []
    for plan in plans:
        latest = await _latest_analysis_for_plan(plan.id, db)
        responses.append(_to_response(plan, latest))
    return responses


@router.get("/crop-plans/{plan_id}", response_model=CropPlanResponse)
async def get_crop_plan(
    plan_id: str,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db_session),
) -> CropPlanResponse:
    plan = await _get_owned_plan(plan_id, current_user.id, db)
    latest = await _latest_analysis_for_plan(plan.id, db)
    return _to_response(plan, latest)


@router.patch("/crop-plans/{plan_id}", response_model=CropPlanResponse)
async def update_crop_plan(
    plan_id: str,
    payload: CropPlanUpdateRequest,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db_session),
) -> CropPlanResponse:
    plan = await _get_owned_plan(plan_id, current_user.id, db)

    if payload.name is not None:
        plan.name = payload.name
    if payload.risk_tolerance is not None:
        plan.risk_tolerance = payload.risk_tolerance
    if payload.is_archived is not None:
        plan.is_archived = payload.is_archived

    # Store a naive UTC datetime to match DB column type
    plan.updated_at = datetime.utcnow()
    await db.commit()
    await db.refresh(plan)

    latest = await _latest_analysis_for_plan(plan.id, db)
    return _to_response(plan, latest)


@router.delete("/crop-plans/{plan_id}", status_code=204)
async def delete_crop_plan(
    plan_id: str,
    current_user: User = Depends(get_current_user),
    db: AsyncSession = Depends(get_db_session),
) -> None:
    plan = await _get_owned_plan(plan_id, current_user.id, db)

    existing_analysis = await _latest_analysis_for_plan(plan.id, db)
    if existing_analysis is not None:
        raise CropPlanHasAnalysesError(
            message="This plan has analysis history and cannot be deleted. "
                    "Archive it instead to hide it from your active plans."
        )

    await db.delete(plan)
    await db.commit()
