from collections.abc import Iterable, Sequence

import structlog
from sqlalchemy import and_, delete, func, select
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload

from app.database.models import (
    Poll,
    PollAnswer,
    PollOption,
    PollQuestion,
    PollResponse,
)


logger = structlog.get_logger(__name__)


async def create_poll(
    db: AsyncSession,
    *,
    title: str,
    description: str | None,
    reward_enabled: bool,
    reward_amount_kopeks: int,
    created_by: int | None,
    questions: Sequence[dict[str, Iterable[str]]],
) -> Poll:
    poll = Poll(
        title=title,
        description=description,
        reward_enabled=reward_enabled,
        reward_amount_kopeks=reward_amount_kopeks if reward_enabled else 0,
        created_by=created_by,
    )
    db.add(poll)
    await db.flush()

    for order, question_data in enumerate(questions, start=1):
        question_text = question_data.get('text', '').strip()
        if not question_text:
            continue

        question = PollQuestion(
            poll_id=poll.id,
            text=question_text,
            order=order,
        )
        db.add(question)
        await db.flush()

        for option_order, option_text in enumerate(question_data.get('options', []), start=1):
            option_text = option_text.strip()
            if not option_text:
                continue
            option = PollOption(
                question_id=question.id,
                text=option_text,
                order=option_order,
            )
            db.add(option)

    await db.commit()
    await db.refresh(
        poll,
        attribute_names=['questions'],
    )
    return poll


async def list_polls(db: AsyncSession) -> list[Poll]:
    result = await db.execute(
        select(Poll)
        .options(selectinload(Poll.questions).options(selectinload(PollQuestion.options)))
        .order_by(Poll.created_at.desc())
    )
    return result.scalars().all()


async def get_poll_by_id(db: AsyncSession, poll_id: int) -> Poll | None:
    result = await db.execute(
        select(Poll)
        .options(
            selectinload(Poll.questions).options(selectinload(PollQuestion.options)),
            selectinload(Poll.responses),
        )
        .where(Poll.id == poll_id)
    )
    return result.scalar_one_or_none()


async def delete_poll(db: AsyncSession, poll_id: int) -> bool:
    poll = await db.get(Poll, poll_id)
    if not poll:
        return False

    await db.delete(poll)
    await db.commit()
    logger.info('🗑️ Удалён опрос', poll_id=poll_id)
    return True


async def create_poll_response(
    db: AsyncSession,
    poll_id: int,
    user_id: int,
) -> PollResponse:
    result = await db.execute(
        select(PollResponse).where(
            and_(
                PollResponse.poll_id == poll_id,
                PollResponse.user_id == user_id,
            )
        )
    )
    response = result.scalar_one_or_none()
    if response:
        return response

    response = PollResponse(
        poll_id=poll_id,
        user_id=user_id,
    )
    db.add(response)
    await db.commit()
    await db.refresh(response)
    return response


async def get_poll_response_by_id(
    db: AsyncSession,
    response_id: int,
) -> PollResponse | None:
    result = await db.execute(
        select(PollResponse)
        .options(
            selectinload(PollResponse.poll).options(
                selectinload(Poll.questions).options(selectinload(PollQuestion.options))
            ),
            selectinload(PollResponse.answers),
            selectinload(PollResponse.user),
        )
        .where(PollResponse.id == response_id)
    )
    return result.scalar_one_or_none()


async def record_poll_answer(
    db: AsyncSession,
    *,
    response_id: int,
    question_id: int,
    option_id: int,
) -> PollAnswer:
    result = await db.execute(
        select(PollAnswer).where(
            and_(
                PollAnswer.response_id == response_id,
                PollAnswer.question_id == question_id,
            )
        )
    )
    answer = result.scalar_one_or_none()
    if answer:
        answer.option_id = option_id
        await db.commit()
        await db.refresh(answer)
        return answer

    answer = PollAnswer(
        response_id=response_id,
        question_id=question_id,
        option_id=option_id,
    )
    db.add(answer)
    await db.commit()
    await db.refresh(answer)
    return answer


async def reset_poll_answers(db: AsyncSession, response_id: int) -> None:
    await db.execute(delete(PollAnswer).where(PollAnswer.response_id == response_id))
    await db.commit()


async def get_poll_statistics(db: AsyncSession, poll_id: int) -> dict:
    totals_result = await db.execute(
        select(
            func.count(PollResponse.id),
            func.count(PollResponse.completed_at),
            func.coalesce(func.sum(PollResponse.reward_amount_kopeks), 0),
        ).where(PollResponse.poll_id == poll_id)
    )
    total_responses, completed_responses, reward_sum = totals_result.one()

    option_counts_result = await db.execute(
        select(
            PollQuestion.id,
            PollQuestion.text,
            PollQuestion.order,
            PollOption.id,
            PollOption.text,
            PollOption.order,
            func.count(PollAnswer.id),
        )
        .join(PollOption, PollOption.question_id == PollQuestion.id)
        .outerjoin(
            PollAnswer,
            and_(
                PollAnswer.question_id == PollQuestion.id,
                PollAnswer.option_id == PollOption.id,
            ),
        )
        .where(PollQuestion.poll_id == poll_id)
        .group_by(
            PollQuestion.id,
            PollQuestion.text,
            PollQuestion.order,
            PollOption.id,
            PollOption.text,
            PollOption.order,
        )
        .order_by(PollQuestion.order.asc(), PollOption.order.asc())
    )

    questions_map: dict[int, dict] = {}
    for (
        question_id,
        question_text,
        question_order,
        option_id,
        option_text,
        option_order,
        answer_count,
    ) in option_counts_result:
        question_entry = questions_map.setdefault(
            question_id,
            {
                'id': question_id,
                'text': question_text,
                'order': question_order,
                'options': [],
            },
        )
        question_entry['options'].append(
            {
                'id': option_id,
                'text': option_text,
                'count': answer_count,
            }
        )

    questions = sorted(questions_map.values(), key=lambda item: item['order'])

    return {
        'total_responses': total_responses,
        'completed_responses': completed_responses,
        'reward_sum_kopeks': reward_sum,
        'questions': questions,
    }


async def get_poll_responses_with_answers(
    db: AsyncSession,
    poll_id: int,
    *,
    limit: int,
    offset: int,
) -> tuple[list[PollResponse], int]:
    total_result = await db.execute(
        select(func.count()).select_from(PollResponse).where(PollResponse.poll_id == poll_id)
    )
    total = int(total_result.scalar_one() or 0)

    if total == 0:
        return [], 0

    result = await db.execute(
        select(PollResponse)
        .options(
            selectinload(PollResponse.user),
            selectinload(PollResponse.answers).selectinload(PollAnswer.question),
            selectinload(PollResponse.answers).selectinload(PollAnswer.option),
        )
        .where(PollResponse.poll_id == poll_id)
        .order_by(PollResponse.sent_at.asc())
        .offset(offset)
        .limit(limit)
    )

    responses = result.scalars().unique().all()
    return responses, total
