package adapter

import (
	"context"
	"fmt"
	"net/url"
	"time"

	"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
	"gitea.dwysokinski.me/twhelp/corev3/internal/tw"
)

type TWHTTP struct {
	client *tw.Client
}

func NewTWHTTP(client *tw.Client) *TWHTTP {
	return &TWHTTP{client: client}
}

func (t *TWHTTP) GetOpenServers(ctx context.Context, baseURL *url.URL) (domain.BaseServers, error) {
	servers, err := t.client.GetOpenServers(ctx, baseURL)
	if err != nil {
		return nil, err
	}

	return t.convertServersToDomain(servers)
}

func (t *TWHTTP) convertServersToDomain(servers []tw.Server) (domain.BaseServers, error) {
	res := make(domain.BaseServers, 0, len(servers))

	for _, s := range servers {
		converted, err := domain.NewBaseServer(s.Key, s.URL, true)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.BaseServer: %w", err)
		}

		res = append(res, converted)
	}

	return res, nil
}

func (t *TWHTTP) GetServerConfig(ctx context.Context, baseURL *url.URL) (domain.ServerConfig, error) {
	cfg, err := t.client.GetServerConfig(ctx, baseURL)
	if err != nil {
		return domain.ServerConfig{}, err
	}

	res, err := domain.NewServerConfig(
		cfg.Speed,
		cfg.UnitSpeed,
		cfg.Moral,
		domain.ServerConfigBuild(cfg.Build),
		domain.ServerConfigMisc(cfg.Misc),
		domain.ServerConfigCommands(cfg.Commands),
		domain.ServerConfigNewbie(cfg.Newbie),
		domain.ServerConfigGame{
			BuildtimeFormula:   cfg.Game.BuildtimeFormula,
			Knight:             cfg.Game.Knight,
			KnightNewItems:     cfg.Game.KnightNewItems.Int(),
			Archer:             cfg.Game.Archer,
			Tech:               cfg.Game.Tech,
			FarmLimit:          cfg.Game.FarmLimit,
			Church:             cfg.Game.Church,
			Watchtower:         cfg.Game.Watchtower,
			Stronghold:         cfg.Game.Stronghold,
			FakeLimit:          cfg.Game.FakeLimit,
			BarbarianRise:      cfg.Game.BarbarianRise,
			BarbarianShrink:    cfg.Game.BarbarianShrink,
			BarbarianMaxPoints: cfg.Game.BarbarianMaxPoints,
			Scavenging:         cfg.Game.Scavenging,
			Hauls:              cfg.Game.Hauls,
			HaulsBase:          cfg.Game.HaulsBase,
			HaulsMax:           cfg.Game.HaulsMax,
			BaseProduction:     cfg.Game.BaseProduction,
			Event:              cfg.Game.Event,
			SuppressEvents:     cfg.Game.SuppressEvents,
		},
		domain.ServerConfigBuildings(cfg.Buildings),
		domain.ServerConfigSnob(cfg.Snob),
		domain.ServerConfigAlly(cfg.Ally),
		domain.ServerConfigCoord(cfg.Coord),
		domain.ServerConfigSitter(cfg.Sitter),
		domain.ServerConfigSleep(cfg.Sleep),
		domain.ServerConfigNight(cfg.Night),
		domain.ServerConfigWin(cfg.Win),
	)
	if err != nil {
		return domain.ServerConfig{}, fmt.Errorf("couldn't construct domain.ServerConfig: %w", err)
	}

	return res, nil
}

func (t *TWHTTP) GetUnitInfo(ctx context.Context, baseURL *url.URL) (domain.UnitInfo, error) {
	info, err := t.client.GetUnitInfo(ctx, baseURL)
	if err != nil {
		return domain.UnitInfo{}, err
	}

	res, err := domain.NewUnitInfo(
		domain.Unit(info.Spear),
		domain.Unit(info.Sword),
		domain.Unit(info.Axe),
		domain.Unit(info.Archer),
		domain.Unit(info.Spy),
		domain.Unit(info.Light),
		domain.Unit(info.Marcher),
		domain.Unit(info.Heavy),
		domain.Unit(info.Ram),
		domain.Unit(info.Catapult),
		domain.Unit(info.Knight),
		domain.Unit(info.Snob),
		domain.Unit(info.Militia),
	)
	if err != nil {
		return domain.UnitInfo{}, fmt.Errorf("couldn't construct domain.UnitInfo: %w", err)
	}

	return res, nil
}

func (t *TWHTTP) GetBuildingInfo(ctx context.Context, baseURL *url.URL) (domain.BuildingInfo, error) {
	info, err := t.client.GetBuildingInfo(ctx, baseURL)
	if err != nil {
		return domain.BuildingInfo{}, err
	}

	res, err := domain.NewBuildingInfo(
		domain.Building(info.Main),
		domain.Building(info.Barracks),
		domain.Building(info.Stable),
		domain.Building(info.Garage),
		domain.Building(info.Watchtower),
		domain.Building(info.Snob),
		domain.Building(info.Smith),
		domain.Building(info.Place),
		domain.Building(info.Statue),
		domain.Building(info.Market),
		domain.Building(info.Wood),
		domain.Building(info.Stone),
		domain.Building(info.Iron),
		domain.Building(info.Farm),
		domain.Building(info.Storage),
		domain.Building(info.Hide),
		domain.Building(info.Wall),
	)
	if err != nil {
		return domain.BuildingInfo{}, fmt.Errorf("couldn't construct domain.BuildingInfo: %w", err)
	}

	return res, nil
}

func (t *TWHTTP) GetTribes(ctx context.Context, baseURL *url.URL) (domain.BaseTribes, error) {
	tribes, err := t.client.GetTribes(ctx, baseURL)
	if err != nil {
		return nil, err
	}

	return t.convertTribesToDomain(tribes)
}

func (t *TWHTTP) convertTribesToDomain(tribes []tw.Tribe) (domain.BaseTribes, error) {
	res := make(domain.BaseTribes, 0, len(tribes))

	for _, tr := range tribes {
		od, err := domain.NewOpponentsDefeated(
			tr.OpponentsDefeated.RankAtt,
			tr.OpponentsDefeated.ScoreAtt,
			tr.OpponentsDefeated.RankDef,
			tr.OpponentsDefeated.ScoreDef,
			tr.OpponentsDefeated.RankSup,
			tr.OpponentsDefeated.ScoreSup,
			tr.OpponentsDefeated.RankTotal,
			tr.OpponentsDefeated.ScoreTotal,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.OpponentsDefeated: %w", err)
		}

		converted, err := domain.NewBaseTribe(
			tr.ID,
			tr.Name,
			tr.Tag,
			tr.NumMembers,
			tr.NumVillages,
			tr.Points,
			tr.AllPoints,
			tr.Rank,
			od,
			tr.ProfileURL,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.BaseTribe: %w", err)
		}

		res = append(res, converted)
	}

	return res, nil
}

func (t *TWHTTP) GetPlayers(ctx context.Context, baseURL *url.URL) (domain.BasePlayers, error) {
	players, err := t.client.GetPlayers(ctx, baseURL)
	if err != nil {
		return nil, err
	}

	return t.convertPlayersToDomain(players)
}

func (t *TWHTTP) convertPlayersToDomain(players []tw.Player) (domain.BasePlayers, error) {
	res := make(domain.BasePlayers, 0, len(players))

	for _, p := range players {
		od, err := domain.NewOpponentsDefeated(
			p.OpponentsDefeated.RankAtt,
			p.OpponentsDefeated.ScoreAtt,
			p.OpponentsDefeated.RankDef,
			p.OpponentsDefeated.ScoreDef,
			p.OpponentsDefeated.RankSup,
			p.OpponentsDefeated.ScoreSup,
			p.OpponentsDefeated.RankTotal,
			p.OpponentsDefeated.ScoreTotal,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.OpponentsDefeated: %w", err)
		}

		converted, err := domain.NewBasePlayer(
			p.ID,
			p.Name,
			p.NumVillages,
			p.Points,
			p.Rank,
			p.TribeID,
			od,
			p.ProfileURL,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.BasePlayer: %w", err)
		}

		res = append(res, converted)
	}

	return res, nil
}

func (t *TWHTTP) GetVillages(ctx context.Context, baseURL *url.URL) (domain.BaseVillages, error) {
	villages, err := t.client.GetVillages(ctx, baseURL)
	if err != nil {
		return nil, err
	}

	return t.convertVillagesToDomain(villages)
}

func (t *TWHTTP) convertVillagesToDomain(villages []tw.Village) (domain.BaseVillages, error) {
	res := make(domain.BaseVillages, 0, len(villages))

	for _, v := range villages {
		converted, err := domain.NewBaseVillage(
			v.ID,
			v.Name,
			v.Points,
			v.X,
			v.Y,
			v.Continent,
			v.Bonus,
			v.PlayerID,
			v.ProfileURL,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.BaseVillage: %w", err)
		}

		res = append(res, converted)
	}

	return res, nil
}

func (t *TWHTTP) GetEnnoblements(
	ctx context.Context,
	baseURL *url.URL,
	since time.Time,
) (domain.BaseEnnoblements, error) {
	ennoblements, err := t.client.GetEnnoblements(ctx, baseURL, since)
	if err != nil {
		return nil, err
	}

	return t.convertEnnoblementsToDomain(ennoblements)
}

func (t *TWHTTP) convertEnnoblementsToDomain(ennoblements []tw.Ennoblement) (domain.BaseEnnoblements, error) {
	res := make(domain.BaseEnnoblements, 0, len(ennoblements))

	for _, e := range ennoblements {
		converted, err := domain.NewBaseEnnoblement(
			e.VillageID,
			e.NewOwnerID,
			e.NewTribeID,
			e.OldOwnerID,
			e.OldTribeID,
			e.Points,
			e.CreatedAt,
		)
		if err != nil {
			return nil, fmt.Errorf("couldn't construct domain.BaseEnnoblement: %w", err)
		}

		res = append(res, converted)
	}

	return res, nil
}
