from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Literal
import copy
import logging
import pydantic

from ..db_engine import db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors

logger = logging.getLogger('discord_app')

router = APIRouter(
    prefix='/api/v3/decisions',
    tags=['decisions']
)


class DecisionModel(pydantic.BaseModel):
    game_id: int
    season: int
    week: int
    game_num: int
    pitcher_id: int
    team_id: int
    win: int = 0
    loss: int = 0
    hold: int = 0
    is_save: int = 0
    is_start: bool = False
    b_save: int = 0
    irunners: int = 0
    irunners_scored: int = 0
    rest_ip: float = 0
    rest_required: int = 0


class DecisionList(pydantic.BaseModel):
    decisions: List[DecisionModel]


class DecisionReturnList(pydantic.BaseModel):
    count: int
    decisions: list[DecisionModel]


@router.get('')
@handle_db_errors
async def get_decisions(
        season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None),
        s_type: Literal['regular', 'post', 'all', None] = None, team_id: list = Query(default=None),
        week_start: Optional[int] = None, week_end: Optional[int] = None, win: Optional[int] = None,
        loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None,
        b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None),
        game_id: list = Query(default=None), player_id: list = Query(default=None),
        limit: Optional[int] = None, short_output: Optional[bool] = False):
    all_dec = Decision.select().order_by(-Decision.season, -Decision.week, -Decision.game_num)

    if season is not None:
        all_dec = all_dec.where(Decision.season << season)
    if week is not None:
        all_dec = all_dec.where(Decision.week << week)
    if game_num is not None:
        all_dec = all_dec.where(Decision.game_num << game_num)
    if game_id is not None:
        all_dec = all_dec.where(Decision.game_id << game_id)
    if player_id is not None:
        all_dec = all_dec.where(Decision.pitcher << player_id)
    # # Need to allow for split-season stats
    # if team_id is not None:
    #     all_teams = Team.select().where(Team.id << team_id)
    #     all_games = StratGame.select().where(
    #         (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams))
    #     all_dec = all_dec.where(Decision.game << all_games)
    # if team_id is not None:
    #     all_players = Player.select().where(Player.team_id << team_id)
    #     all_dec = all_dec.where(Decision.pitcher << all_players)
    if team_id is not None:
        s8_teams = [int(x) for x in team_id if int(x) <= 350]
        if season is not None and 8 in season or s8_teams:
            all_teams = Team.select().where(Team.id << team_id)
            all_games = StratGame.select().where(
                (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams))
            all_dec = all_dec.where(Decision.game << all_games)
        else:
            all_teams = Team.select().where(Team.id << team_id)
            all_dec = all_dec.where(Decision.team << all_teams)
    if s_type is not None:
        all_games = StratGame.select().where(StratGame.season_type == s_type)
        all_dec = all_dec.where(Decision.game << all_games)
    # if team_id is not None:
    #     all_players = Player.select().where(Player.team_id << team_id)
    #     all_dec = all_dec.where(Decision.pitcher << all_players)
    if week_start is not None:
        all_dec = all_dec.where(Decision.week >= week_start)
    if week_end is not None:
        all_dec = all_dec.where(Decision.week <= week_end)
    if win is not None:
        all_dec = all_dec.where(Decision.win == win)
    if loss is not None:
        all_dec = all_dec.where(Decision.loss == loss)
    if hold is not None:
        all_dec = all_dec.where(Decision.hold == hold)
    if save is not None:
        all_dec = all_dec.where(Decision.save == save)
    if b_save is not None:
        all_dec = all_dec.where(Decision.b_save == b_save)
    if irunners is not None:
        all_dec = all_dec.where(Decision.irunners << irunners)
    if irunners_scored is not None:
        all_dec = all_dec.where(Decision.irunners_scored << irunners_scored)

    if limit is not None:
        if limit < 1:
            limit = 1
        all_dec = all_dec.limit(limit)

    return_dec = {
        'count': all_dec.count(),
        'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec]
    }
    db.close()
    return return_dec


@router.patch('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def patch_decision(
        decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None,
        save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None,
        irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None,
        token: str = Depends(oauth2_scheme)):
    if not valid_token(token):
        logger.warning(f'patch_decision - Bad Token: {token}')
        raise HTTPException(status_code=401, detail='Unauthorized')

    this_dec = Decision.get_or_none(Decision.id == decision_id)
    if this_dec is None:
        db.close()
        raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found')

    if win is not None:
        this_dec.win = win
    if loss is not None:
        this_dec.loss = loss
    if hold is not None:
        this_dec.hold = hold
    if save is not None:
        this_dec.is_save = save
    if b_save is not None:
        this_dec.b_save = b_save
    if irunners is not None:
        this_dec.irunners = irunners
    if irunners_scored is not None:
        this_dec.irunners_scored = irunners_scored
    if rest_ip is not None:
        this_dec.rest_ip = rest_ip
    if rest_required is not None:
        this_dec.rest_required = rest_required

    if this_dec.save() == 1:
        d_result = model_to_dict(this_dec)
        db.close()
        return d_result
    else:
        db.close()
        raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}')


@router.post('', include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
    if not valid_token(token):
        logger.warning(f'post_decisions - Bad Token: {token}')
        raise HTTPException(status_code=401, detail='Unauthorized')

    new_dec = []
    for x in dec_list.decisions:
        if StratGame.get_or_none(StratGame.id == x.game_id) is None:
            raise HTTPException(status_code=404, detail=f'Game ID {x.game_id} not found')
        if Player.get_or_none(Player.id == x.pitcher_id) is None:
            raise HTTPException(status_code=404, detail=f'Player ID {x.pitcher_id} not found')

        new_dec.append(x.dict())

    with db.atomic():
        for batch in chunked(new_dec, 10):
            Decision.insert_many(batch).on_conflict_ignore().execute()
    db.close()

    return f'Inserted {len(new_dec)} decisions'


@router.delete('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
    if not valid_token(token):
        logger.warning(f'delete_decision - Bad Token: {token}')
        raise HTTPException(status_code=401, detail='Unauthorized')

    this_dec = Decision.get_or_none(Decision.id == decision_id)
    if this_dec is None:
        db.close()
        raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found')

    count = this_dec.delete_instance()
    db.close()

    if count == 1:
        return f'Decision {decision_id} has been deleted'
    else:
        raise HTTPException(status_code=500, detail=f'Decision {decision_id} could not be deleted')


@router.delete('/game/{game_id}', include_in_schema=PRIVATE_IN_SCHEMA)
@handle_db_errors
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
    if not valid_token(token):
        logger.warning(f'delete_decisions_game - Bad Token: {token}')
        raise HTTPException(status_code=401, detail='Unauthorized')

    this_game = StratGame.get_or_none(StratGame.id == game_id)
    if not this_game:
        db.close()
        raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found')

    count = Decision.delete().where(Decision.game == this_game).execute()
    db.close()

    if count > 0:
        return f'Deleted {count} decisions matching Game ID {game_id}'
    else:
        raise HTTPException(status_code=500, detail=f'No decisions matching Game ID {game_id} were deleted')



