import { API_BASE } from '../shared/constants';
import { getTelegramInitData } from '../shared/telegram';
import type { ServerActiveBuffRow, ServerActiveDebuffRow } from './buffMap';

/**
 * REST API client for /api/v1/* endpoints.
 *
 * All requests include Telegram initData for authentication.
 * Responses are typed via generics.
 */

export class ApiError extends Error {
  constructor(
    public status: number,
    public statusText: string,
    public body: string,
  ) {
    super(`API Error ${status}: ${statusText}`);
    this.name = 'ApiError';
  }
}

/** Build headers with Telegram auth */
function buildHeaders(extra?: Record<string, string>): Record<string, string> {
  const headers: Record<string, string> = {
    'Content-Type': 'application/json',
    ...extra,
  };

  const initData = getTelegramInitData();
  if (initData) {
    headers['X-Telegram-Init-Data'] = initData;
  }

  return headers;
}

/** Parse response or throw ApiError */
async function handleResponse<T>(res: Response): Promise<T> {
  if (!res.ok) {
    const body = await res.text().catch(() => '');
    throw new ApiError(res.status, res.statusText, body);
  }

  // 204 No Content
  if (res.status === 204) {
    return undefined as T;
  }

  return res.json() as Promise<T>;
}

/** GET request */
export async function apiGet<T>(path: string): Promise<T> {
  const res = await fetch(`${API_BASE}${path}`, {
    method: 'GET',
    headers: buildHeaders(),
  });
  return handleResponse<T>(res);
}

/** POST request */
export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
  const res = await fetch(`${API_BASE}${path}`, {
    method: 'POST',
    headers: buildHeaders(),
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });
  return handleResponse<T>(res);
}

/** PUT request */
export async function apiPut<T>(path: string, body?: unknown): Promise<T> {
  const res = await fetch(`${API_BASE}${path}`, {
    method: 'PUT',
    headers: buildHeaders(),
    body: body !== undefined ? JSON.stringify(body) : undefined,
  });
  return handleResponse<T>(res);
}

/** DELETE request */
export async function apiDelete<T>(path: string): Promise<T> {
  const res = await fetch(`${API_BASE}${path}`, {
    method: 'DELETE',
    headers: buildHeaders(),
  });
  return handleResponse<T>(res);
}

// ---- Typed API Methods ----

export interface HeroResponse {
  id: number;
  telegramId: number;
  name: string;
  hp: number;
  maxHp: number;
  attack: number;
  defense: number;
  speed: number;
  attackSpeed?: number;
  attackPower?: number;
  defensePower?: number;
  strength: number;
  constitution: number;
  agility: number;
  luck: number;
  state: string;
  restKind?: string;
  weaponId: number;
  armorId: number;
  weapon: WeaponResponse | null;
  armor: ArmorResponse | null;
  gold: number;
  xp: number;
  xpToNext?: number;
  level: number;
  reviveCount?: number;
  subscriptionActive?: boolean;
  buffFreeChargesRemaining?: number;
  buffQuotaPeriodEnd?: string;
  buffCharges?: Record<string, { remaining: number; periodEnd: string | null }>;
  potions?: number;
  positionX?: number;
  positionY?: number;
  moveSpeed?: number;
  buffs?: ServerActiveBuffRow[];
  debuffs?: ServerActiveDebuffRow[];
  /** Extended equipment slots (§6.3) keyed by slot name */
  equipment?: Record<string, {
    id: number;
    slot: string;
    formId?: string;
    name: string;
    rarity: string;
    ilvl?: number;
    primaryStat?: number;
    statType?: string;
  }>;
  /** Same slot data as `equipment`; WebSocket `hero_state` from Go uses `gear` */
  gear?: HeroResponse['equipment'];
  /** Unequipped gear (backpack), max 40 — mirrors Go `Hero.Inventory` */
  inventory?: Array<{
    id: number;
    slot: string;
    formId?: string;
    name: string;
    rarity: string;
    ilvl?: number;
    primaryStat?: number;
    statType?: string;
  }>;
}

export interface AuthResponse {
  token: string;
  /** 0 if the player has not created a hero yet. */
  heroId: number;
}

/** Authenticate via Telegram initData, returns session info */
export async function authenticate(): Promise<AuthResponse> {
  return apiPost<AuthResponse>('/auth/telegram');
}

/** Get current hero state */
export async function getHero(telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<HeroResponse>(`/hero${query}`);
}

export interface OfflineReport {
  offlineSeconds: number;
  monstersKilled: number;
  xpGained: number;
  goldGained: number;
  levelsGained: number;
  message: string;
}

export interface InitHeroResponse {
  /** Null until the player submits a valid name (no DB row until then). */
  hero: HeroResponse | null;
  offlineReport: OfflineReport | null;
  mapRef: MapRefResponse;
  needsName?: boolean;
}

/** Initialize or retrieve a hero from the backend (creates on first call) */
export async function initHero(telegramId?: number): Promise<InitHeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<InitHeroResponse>(`/hero/init${query}`);
}

/** Set the hero's display name (first time only). Returns updated hero on success. */
export async function setHeroName(name: string, telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/name${query}`, { name });
}

export interface MapRefResponse {
  mapId: string;
  mapVersion: string;
  etag: string;
  biome: string;
  recommendedLevelMin: number;
  recommendedLevelMax: number;
}

export interface MapTileResponse {
  x: number;
  y: number;
  terrain: string;
}

export interface MapObjectResponse {
  id: string;
  type: string;
  x: number;
  y: number;
}

export interface SpawnPointResponse {
  id: string;
  kind: string;
  x: number;
  y: number;
}

export interface ServerMapResponse {
  mapId: string;
  mapVersion: string;
  biome: string;
  recommendedLevelMin: number;
  recommendedLevelMax: number;
  width: number;
  height: number;
  tiles: MapTileResponse[];
  objects: MapObjectResponse[];
  spawnPoints: SpawnPointResponse[];
}

export interface GetMapResult {
  map: ServerMapResponse | null;
  etag: string;
  notModified: boolean;
}

/** Get map payload by map ID with optional ETag conditional fetch. */
export async function getMap(mapId: string, etag?: string): Promise<GetMapResult> {
  const headers = buildHeaders();
  if (etag) {
    headers['If-None-Match'] = etag;
  }

  const res = await fetch(`${API_BASE}/maps/${encodeURIComponent(mapId)}`, {
    method: 'GET',
    headers,
  });

  const responseEtag = res.headers.get('ETag') ?? etag ?? '';
  if (res.status === 304) {
    return {
      map: null,
      etag: responseEtag,
      notModified: true,
    };
  }
  if (!res.ok) {
    const body = await res.text().catch(() => '');
    throw new ApiError(res.status, res.statusText, body);
  }

  return {
    map: await res.json() as ServerMapResponse,
    etag: responseEtag,
    notModified: false,
  };
}

export interface ActivateBuffResponse {
  buff: ServerActiveBuffRow;
  heroBuffs: ServerActiveBuffRow[];
  hero?: HeroResponse;
}

/** Activate a buff by type */
export async function activateBuff(
  buffType: string,
  telegramId?: number,
): Promise<ActivateBuffResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<ActivateBuffResponse>(`/hero/buff/${buffType}${query}`);
}

/** Request revive after death — returns authoritative hero snapshot */
export async function requestRevive(telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/revive${query}`);
}

export interface VictoryDropResponse {
  itemType: string;
  itemId?: number;
  itemName?: string;
  rarity: string;
  goldAmount: number;
}

export interface ReportVictoryResponse {
  hero: HeroResponse;
  drops: VictoryDropResponse[];
}

/** Apply server-side kill rewards (XP, gold, loot rolls, HP rules) after client-resolved combat */
export async function reportVictory(
  body: { enemyType: string; heroHp: number; positionX?: number; positionY?: number },
  telegramId?: number,
): Promise<ReportVictoryResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<ReportVictoryResponse>(`/hero/victory${query}`, body);
}

// ---- Loot & Equipment ----

export interface LootResponse {
  itemType: 'weapon' | 'armor' | 'gold';
  itemName?: string;
  rarity: string;
  goldAmount: number;
}

export interface WeaponResponse {
  id: number;
  name: string;
  type: string;
  rarity: string;
  damage: number;
  speedMultiplier: number;
  ilvl?: number;
}

export interface ArmorResponse {
  id: number;
  name: string;
  type: string;
  rarity: string;
  defense: number;
  speedMultiplier: number;
  ilvl?: number;
}

export interface EncounterEnemyResponse {
  id: number;
  name: string;
  hp: number;
  maxHp: number;
  attack: number;
  defense: number;
  speed: number;
  enemyType: string;
}

/** Fetch extended equipment slots for the hero */
export async function getHeroEquipment(telegramId?: number): Promise<Record<string, EquipmentItemResponse>> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<Record<string, EquipmentItemResponse>>(`/hero/equipment${query}`);
}

export interface EquipmentItemResponse {
  id: number;
  slot: string;
  formId: string;
  name: string;
  rarity: string;
  ilvl: number;
  basePrimary: number;
  primaryStat: number;
  statType: string;
}

/** Ask backend to generate the next encounter enemy for this hero. */
export async function requestEncounter(telegramId?: number, posX?: number, posY?: number): Promise<EncounterEnemyResponse> {
  const params = new URLSearchParams();
  if (telegramId != null) params.set('telegramId', String(telegramId));
  if (posX != null) params.set('posX', posX.toFixed(2));
  if (posY != null) params.set('posY', posY.toFixed(2));
  const query = params.toString() ? `?${params.toString()}` : '';
  return apiPost<EncounterEnemyResponse>(`/hero/encounter${query}`);
}

/** Get recent loot drops for the hero */
export async function getLoot(): Promise<LootResponse[]> {
  return apiGet<LootResponse[]>('/hero/loot');
}

/** Get available weapons catalog */
export async function getWeapons(): Promise<WeaponResponse[]> {
  return apiGet<WeaponResponse[]>('/weapons');
}

/** Get available armor catalog */
export async function getArmor(): Promise<ArmorResponse[]> {
  return apiGet<ArmorResponse[]>('/armor');
}

// ---- Adventure Log ----

export interface LogEntry {
  id: number;
  message: string;
  createdAt: string;
}

/** Fetch recent adventure log entries (offline events, etc.) */
export async function getAdventureLog(telegramId?: number, limit?: number): Promise<LogEntry[]> {
  const params = new URLSearchParams();
  if (telegramId != null) params.set('telegramId', String(telegramId));
  if (limit != null) params.set('limit', String(limit));
  const query = params.toString() ? `?${params.toString()}` : '';
  const raw = await apiGet<{ log?: LogEntry[] } | LogEntry[]>(`/hero/log${query}`);
  if (Array.isArray(raw)) {
    return raw;
  }
  return raw.log ?? [];
}

// ---- Potions ----

/** Use a healing potion; returns updated hero state */
export async function usePotion(telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/use-potion${query}`);
}

// ---- Towns ----

import type { Town, HeroQuest, NPC, Quest, BuildingData } from '../game/types';

/** Fetch all towns */
export async function getTowns(): Promise<Town[]> {
  return apiGet<Town[]>('/towns');
}

/** Fetch NPCs for a town */
export async function getTownNPCs(townId: number): Promise<NPC[]> {
  return apiGet<NPC[]>(`/towns/${townId}/npcs`);
}

/** Fetch buildings for a town */
export async function getTownBuildings(townId: number): Promise<BuildingData[]> {
  return apiGet<BuildingData[]>(`/towns/${townId}/buildings`);
}

/** Fetch available quests from an NPC */
export async function getNPCQuests(npcId: number, telegramId?: number): Promise<Quest[]> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<Quest[]>(`/npcs/${npcId}/quests${query}`);
}

// ---- Hero Quests ----

/** Raw shape from backend — quest details are nested under "quest" */
interface HeroQuestRaw {
  id: number;
  heroId: number;
  questId: number;
  status: string;
  progress: number;
  quest?: {
    id: number;
    npcId: number;
    title: string;
    description: string;
    type: string;
    targetCount: number;
    targetEnemyType?: string;
    targetTownId?: number;
    dropChance: number;
    minLevel: number;
    maxLevel: number;
    rewardXp: number;
    rewardGold: number;
    rewardPotions: number;
  };
  // Also accept flat fields in case backend changes later
  title?: string;
  description?: string;
  type?: string;
  targetCount?: number;
  rewardXp?: number;
  rewardGold?: number;
  rewardPotions?: number;
  npcName?: string;
  townName?: string;
}

function flattenHeroQuest(raw: HeroQuestRaw): HeroQuest {
  const q = raw.quest;
  return {
    id: raw.id,
    questId: raw.questId,
    title: raw.title ?? q?.title ?? '',
    description: raw.description ?? q?.description ?? '',
    type: raw.type ?? q?.type ?? '',
    targetCount: raw.targetCount ?? q?.targetCount ?? 0,
    progress: raw.progress,
    status: (raw.status as HeroQuest['status']) ?? 'accepted',
    rewardXp: raw.rewardXp ?? q?.rewardXp ?? 0,
    rewardGold: raw.rewardGold ?? q?.rewardGold ?? 0,
    rewardPotions: raw.rewardPotions ?? q?.rewardPotions ?? 0,
    npcName: raw.npcName ?? '',
    townName: raw.townName ?? '',
  };
}

/** Fetch hero's active/completed quests */
export async function getHeroQuests(telegramId?: number): Promise<HeroQuest[]> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  const raw = await apiGet<HeroQuestRaw[]>(`/hero/quests${query}`);
  return raw.map(flattenHeroQuest);
}

/** Accept a quest */
export async function acceptQuest(questId: number, telegramId?: number): Promise<HeroQuest> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroQuest>(`/hero/quests/${questId}/accept${query}`);
}

/** Claim a completed quest's rewards */
export async function claimQuest(heroQuestId: number, telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/quests/${heroQuestId}/claim${query}`);
}

/** Abandon a quest */
export async function abandonQuest(heroQuestId: number, telegramId?: number): Promise<void> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiDelete<void>(`/hero/quests/${heroQuestId}${query}`);
}

// ---- NPC Services ----

/** Buy a potion from a merchant NPC (matches backend POST /api/v1/hero/npc-buy-potion) */
export async function buyPotion(telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/npc-buy-potion${query}`);
}

/** Heal to full at a healer NPC (matches backend POST /api/v1/hero/npc-heal) */
export async function healAtNPC(telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/npc-heal${query}`);
}

// ---- NPC Proximity & Interaction ----

export interface NearbyNPCResponse {
  id: number;
  name: string;
  type: string;
  worldX: number;
  worldY: number;
}

export interface NPCInteractResponse {
  npcName: string;
  npcType: string;
  actions: Array<{ type: string; label: string; cost?: number }>;
  quests?: Quest[];
}

/** Fetch NPCs near the hero's current position */
export async function getNearbyNPCs(telegramId?: number, posX?: number, posY?: number): Promise<NearbyNPCResponse[]> {
  const params = new URLSearchParams();
  if (telegramId != null) params.set('telegramId', String(telegramId));
  if (posX != null) params.set('posX', String(posX));
  if (posY != null) params.set('posY', String(posY));
  const query = params.toString() ? `?${params.toString()}` : '';
  return apiGet<NearbyNPCResponse[]>(`/hero/nearby-npcs${query}`);
}

/** Interact with an NPC to get available actions */
export async function interactWithNPC(npcId: number, telegramId?: number): Promise<NPCInteractResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<NPCInteractResponse>(`/npcs/${npcId}/interact${query}`);
}

/** Buy an item from a merchant NPC */
export async function buyFromMerchant(npcId: number, item: string, telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/npcs/${npcId}/buy${query}`, { item });
}

/** Heal at a healer NPC */
export async function healAtHealerNPC(npcId: number, telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/npcs/${npcId}/heal${query}`);
}

// ---- Wandering NPC alms ----

/** Matches backend model.AlmsResponse (POST /hero/npc-alms) */
export interface NPCAlmsResponse {
  accepted: boolean;
  message: string;
  goldSpent?: number;
  itemDrop?: {
    itemType: string;
    itemId?: number;
    itemName?: string;
    rarity: string;
    goldAmount?: number;
  };
  hero?: HeroResponse;
}

/** Give gold to a wandering NPC for a mysterious item */
export async function giveNPCAlms(telegramId?: number): Promise<NPCAlmsResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<NPCAlmsResponse>(`/hero/npc-alms${query}`, { accept: true });
}

/** Purchase a buff charge refill (placeholder for Telegram Payments integration) */
export async function purchaseBuffRefill(
  buffType: string,
  telegramId?: number,
): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/purchase-buff-refill${query}`, { buffType });
}

// ---- Achievements (spec 10.3) ----

export interface Achievement {
  id: string;
  title: string;
  description: string;
  conditionType: string;
  conditionValue: number;
  rewardType: string;
  rewardAmount: number;
  unlocked: boolean;
  unlockedAt?: string;
}

/** Fetch all achievements with unlock status */
export async function getAchievements(telegramId?: number): Promise<Achievement[]> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<Achievement[]>(`/hero/achievements${query}`);
}

// ---- Nearby Heroes (spec 2.3 shared world) ----

export interface NearbyHero {
  id: number;
  name: string;
  level: number;
  positionX: number;
  positionY: number;
}

/** Fetch heroes near the player's current position */
export async function getNearbyHeroes(telegramId?: number): Promise<NearbyHero[]> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<NearbyHero[]>(`/hero/nearby${query}`);
}

// ---- Daily Tasks (backend-driven, spec 10.1) ----

export interface DailyTaskResponse {
  taskId: string;
  title: string;
  description: string;
  objectiveType: string;
  objectiveCount: number;
  progress: number;
  completed: boolean;
  claimed: boolean;
  rewardType: string;
  rewardAmount: number;
  period: string;
}

/** Fetch daily tasks with progress */
export async function getDailyTasks(telegramId?: number): Promise<DailyTaskResponse[]> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiGet<DailyTaskResponse[]>(`/hero/daily-tasks${query}`);
}

/** Claim a completed daily task reward */
export async function claimDailyTask(taskId: string, telegramId?: number): Promise<HeroResponse> {
  const query = telegramId != null ? `?telegramId=${telegramId}` : '';
  return apiPost<HeroResponse>(`/hero/daily-tasks/${encodeURIComponent(taskId)}/claim${query}`);
}
