package game_service

import (
	"context"
	"evening_detective_server/internal/modules/cleaner"
	"evening_detective_server/internal/modules/password_generator"
	"evening_detective_server/internal/modules/storytelling"
	"evening_detective_server/internal/repos/actions_repo"
	"evening_detective_server/internal/repos/applications_repo"
	"evening_detective_server/internal/repos/games_repo"
	"evening_detective_server/internal/repos/scenarios_repo"
	"evening_detective_server/internal/repos/teams_repo"
	"evening_detective_server/internal/services/scenarios_service"
	"time"
)

type GameService struct {
	gamesRepo         *games_repo.GamesRepo
	teamsRepo         *teams_repo.TeamsRepo
	scenariosRepo     *scenarios_repo.ScenariosRepo
	scenarioService   *scenarios_service.ScenarioService
	domain            string
	passwordGenerator password_generator.IPasswordGenerator
	storyteller       storytelling.IStory
	actionsRepo       *actions_repo.ActionsRepo
	applicationsRepo  *applications_repo.ApplicationsRepo
	cleaner           cleaner.ICleaner
}

func NewGameService(
	gamesRepo *games_repo.GamesRepo,
	teamsRepo *teams_repo.TeamsRepo,
	scenariosRepo *scenarios_repo.ScenariosRepo,
	scenarioService *scenarios_service.ScenarioService,
	domain string,
	passwordGenerator password_generator.IPasswordGenerator,
	storyteller storytelling.IStory,
	actionsRepo *actions_repo.ActionsRepo,
	applicationsRepo *applications_repo.ApplicationsRepo,
	cleaner cleaner.ICleaner,
) *GameService {
	return &GameService{
		gamesRepo:         gamesRepo,
		teamsRepo:         teamsRepo,
		scenariosRepo:     scenariosRepo,
		scenarioService:   scenarioService,
		domain:            domain,
		passwordGenerator: passwordGenerator,
		storyteller:       storyteller,
		actionsRepo:       actionsRepo,
		applicationsRepo:  applicationsRepo,
		cleaner:           cleaner,
	}
}

func (s *GameService) AddGame(
	ctx context.Context,
	name string,
	description string,
	startAt time.Time,
	scenarioId int32,
) (int, error) {
	id, err := s.gamesRepo.AddGame(ctx, name, description, startAt, scenarioId)
	if err != nil {
		return 0, err
	}
	return id, nil
}

func (s *GameService) GetGame(ctx context.Context, id int) (*Game, error) {
	game, err := s.gamesRepo.GetGameByID(ctx, id)
	if err != nil {
		return nil, err
	}
	res := mapGame(game, s.domain)
	return res, nil
}

func (s *GameService) GetFullGame(
	ctx context.Context,
	id int,
) (*Game, error) {
	game, err := s.gamesRepo.GetGameByID(ctx, id)
	if err != nil {
		return nil, err
	}
	scenario, err := s.scenariosRepo.GetScenarioByID(ctx, game.ScenarioId)
	if err != nil {
		return nil, err
	}
	game.Scenario = scenario
	teams, err := s.teamsRepo.GetTeamsByGameID(ctx, id)
	if err != nil {
		return nil, err
	}
	game.Teams = teams

	res := mapGame(game, s.domain)

	teamIds := make([]int, 0, len(game.Teams))
	for _, team := range game.Teams {
		teamIds = append(teamIds, team.ID)
	}

	actionsCountByTeamIDs, err := s.actionsRepo.GetActionsCountByTeamIDs(ctx, teamIds)
	if err != nil {
		return nil, err
	}

	needApplicationsByTeamIDs, err := s.applicationsRepo.GetApplicationsByTeamIDsAndState(ctx, teamIds, "need")
	if err != nil {
		return nil, err
	}

	for _, team := range res.Teams {
		team.ActionsCount = actionsCountByTeamIDs[team.ID]
		team.Applications = mapApplications(needApplicationsByTeamIDs[team.ID])
	}

	return res, nil
}

func (s *GameService) DeleteGame(
	ctx context.Context,
	id int,
) error {
	return s.gamesRepo.DeleteGameByID(ctx, id)
}

func (s *GameService) GetGames(
	ctx context.Context,
) ([]*Game, error) {
	games, err := s.gamesRepo.GetGames(ctx)
	if err != nil {
		return nil, err
	}
	return mapGames(games, s.domain), nil
}

func (s *GameService) UpdateGame(
	ctx context.Context,
	id int,
	name string,
	description string,
	startAt time.Time,
	scenarioId int32,
) error {
	return s.gamesRepo.UpdateGame(ctx, id, name, description, startAt, scenarioId)
}

func (s *GameService) StartGame(ctx context.Context, id int) error {
	return s.gamesRepo.StartGame(ctx, id)
}

func (s *GameService) PauseGame(ctx context.Context, id int) error {
	return s.gamesRepo.PauseGame(ctx, id)
}

func (s *GameService) FinishGame(ctx context.Context, id int) error {
	return s.gamesRepo.FinishGame(ctx, id)
}

func (s *GameService) ResetGame(ctx context.Context, id int) error {
	return s.gamesRepo.ResetGame(ctx, id)
}

func (s *GameService) AddTeam(
	ctx context.Context,
	creatorId int,
	gameId int,
	name string,
) (*Team, error) {
	password, err := s.passwordGenerator.Generate()
	if err != nil {
		return nil, err
	}
	id, err := s.teamsRepo.AddTeam(ctx, creatorId, gameId, name, password)
	if err != nil {
		return nil, err
	}
	return &Team{
		ID:       id,
		Password: password,
	}, nil
}

func (s *GameService) UpdateTeam(ctx context.Context, teamId int, name string) error {
	return s.teamsRepo.UpdateTeam(ctx, teamId, name)
}

func (s *GameService) DeleteTeam(ctx context.Context, teamId int) error {
	return s.teamsRepo.DeleteTeam(ctx, teamId)
}

func (s *GameService) GiveTeamApplications(ctx context.Context, teamId int, name string) error {
	return s.applicationsRepo.UpdateApplicationStatus(ctx, teamId, name, "done")
}

func (s *GameService) GetTeamActions(ctx context.Context, teamId int, password string) (*storytelling.Story, *Game, error) {
	team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
	if err != nil {
		return nil, nil, err
	}

	if team.Password != password {
		return nil, nil, teams_repo.ErrTeamNotFound
	}

	game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
	if err != nil {
		return nil, nil, err
	}

	actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
	if err != nil {
		return nil, nil, err
	}

	scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
	if err != nil {
		return nil, nil, err
	}

	return s.storyteller.GetStory(scenario.Story, actions), mapGame(game, s.domain), nil
}

func (s *GameService) AddTeamAction(ctx context.Context, teamId int, password string, actionCode string) error {
	team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
	if err != nil {
		return err
	}

	if team.Password != password {
		return teams_repo.ErrTeamNotFound
	}

	_, err = s.actionsRepo.AddAction(ctx, teamId, actionCode)
	if err != nil {
		return err
	}

	game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
	if err != nil {
		return err
	}

	actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
	if err != nil {
		return err
	}

	scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
	if err != nil {
		return err
	}

	teamStory := s.storyteller.GetStory(scenario.Story, actions)

	lastPlace := teamStory.Places[len(teamStory.Places)-1]

	for _, application := range lastPlace.Applications {
		_, err := s.applicationsRepo.AddApplication(ctx, teamId, s.cleaner.ClearText(application.Name), application.Image)
		if err != nil {
			return err
		}
	}

	return nil
}

func (s *GameService) DeleteLastTeamAction(ctx context.Context, teamId int) error {
	team, err := s.teamsRepo.GetTeamByID(ctx, teamId)
	if err != nil {
		return err
	}

	game, err := s.gamesRepo.GetGameByID(ctx, team.GameId)
	if err != nil {
		return err
	}

	actions, err := s.actionsRepo.GetActionsByTeamID(ctx, teamId)
	if err != nil {
		return err
	}

	scenario, err := s.scenarioService.GetFullScenarioByID(ctx, game.ScenarioId)
	if err != nil {
		return err
	}

	teamStory := s.storyteller.GetStory(scenario.Story, actions)

	lastPlace := teamStory.Places[len(teamStory.Places)-1]

	for _, application := range lastPlace.Applications {
		if err := s.applicationsRepo.DeleteApplicationByTeamIdAndName(ctx, teamId, application.Name); err != nil {
			return err
		}
	}

	return s.actionsRepo.DeleteLastActionByTeamId(ctx, teamId)
}
