import {
  PrismaClient,
  User,
  Prisma,
  VillageObjectType,
  VillageTile,
} from '@prisma/client';
import { COSTS, REWARDS } from '../utils/economy';
import { applyStreakMultiplier } from '../utils/streak';
import { getTodayDay, isBeforeDay, daysSince } from '../utils/gameDay';

const prisma = new PrismaClient();

/* =========================
   CONSTANTS
========================= */

export const VILLAGE_WIDTH = 5;
export const VILLAGE_HEIGHT = 7;

export const PRODUCING_BUILDINGS = [
  'FIELD',
  'LUMBERJACK',
  'QUARRY',
] as const;

/* =========================
   TYPES
========================= */

type FullVillage = Prisma.VillageGetPayload<{
  include: {
    user: true;
    tiles: { include: { object: true } };
    objects: { include: { tile: true } };
  };
}>;

/* =========================
   PUBLIC API
========================= */

/**
 * Главная точка входа.
 * Синхронизирует day-based прогресс и возвращает актуальное состояние деревни.
 */
export async function syncAndGetVillage(userId: number): Promise<FullVillage> {
  try {
    const today = getTodayDay();

    let villageSnapshot = await fetchVillage(userId);
    if (!villageSnapshot) {
      throw createError({ statusCode: 404, statusMessage: 'Village not found' });
    }

    const user = villageSnapshot.user;

    await prisma.$transaction(async (tx) => {
      await processFinishedClearing(tx, villageSnapshot, today);
      await processFieldExp(tx, villageSnapshot, today);
      await autoStartClearing(tx, villageSnapshot, today);
    });

    // Re-fetch the village state after the transaction to get the latest data including new objects, etc.
    villageSnapshot = await fetchVillage(userId);
    if (!villageSnapshot) {
        throw createError({ statusCode: 404, statusMessage: 'Village not found post-transaction' });
    }

    // --- Enrich tiles with available actions (Step 8 from old getVillageState) ---
    const housesCount = villageSnapshot.objects.filter(o => o.type === 'HOUSE').length;
    const producingCount = villageSnapshot.objects.filter(o => PRODUCING_BUILDINGS.includes(o.type as any)).length;
    const freeWorkers = housesCount - producingCount;

    const tilesWithActions = villageSnapshot.tiles.map(tile => {
      const availableActions: any[] = [];

      // Action: BUILD
      if (tile.terrainType === 'EMPTY' && !tile.object) {
        const buildableObjectTypes: VillageObjectType[] = ['HOUSE', 'FIELD', 'LUMBERJACK', 'QUARRY', 'WELL'];
        const buildActions = buildableObjectTypes.map(buildingType => {
          const cost = COSTS.BUILD[buildingType];
          const isProducing = (PRODUCING_BUILDINGS as readonly string[]).includes(buildingType);
          let isEnabled = user.coins >= cost;
          let disabledReason = user.coins < cost ? 'Not enough coins' : undefined;

          if (isEnabled && isProducing && freeWorkers <= 0) {
            isEnabled = false;
            disabledReason = 'Not enough workers';
          }
          
          return {
            type: 'BUILD',
            buildingType,
            cost,
            isEnabled,
            disabledReason,
          };
        });
        availableActions.push(...buildActions);
      }
      
      return { ...tile, availableActions };
    });

    return { ...villageSnapshot, tiles: tilesWithActions } as FullVillage;
  } catch (error) {
    console.error('Error in syncAndGetVillage:', error);
    throw createError({ statusCode: 500, statusMessage: 'An error occurred during village synchronization.' });
  }
}

/**
 * Генерация деревни для нового пользователя
 */
export async function generateVillageForUser(user: User) {
  // Enforce immutability of the village layout and ensure creation occurs only once.
  await prisma.$transaction(async (tx) => {
    // 1. Find or create Village
    let village = await tx.village.findUnique({
      where: { userId: user.id },
    });

    let villageCreated = false;
    if (!village) {
      village = await tx.village.create({
        data: { userId: user.id },
      });
      villageCreated = true;
    }

    // If village was just created, initialize user resources
    if (villageCreated) {
      await tx.user.update({
        where: { id: user.id },
        data: { coins: 10, exp: 0 },
      });
    }

    // 2. Count existing VillageTiles for this Village
    const tilesCount = await tx.villageTile.count({
      where: { villageId: village!.id }, // village is guaranteed to exist here
    });

    // If tiles already exist, layout is immutable. Do nothing.
    if (tilesCount > 0) {
      // Village layout is immutable once created.
      return;
    }

    // 3. Create tiles ONLY if tilesCount is 0 (broken state or first creation)
    // This logic ensures tiles are created exactly once.
    const tilesToCreate: Omit<
      VillageTile,
      'id' | 'clearingStartedDay' | 'villageId'
    >[] = [];

    const centralXStart = 1;
    const centralXEnd = 4;
    const centralYStart = 2;
    const centralYEnd = 5;

    for (let y = 0; y < VILLAGE_HEIGHT; y++) {
      for (let x = 0; x < VILLAGE_WIDTH; x++) {
        const isCentral =
          x >= centralXStart &&
          x < centralXEnd &&
          y >= centralYStart &&
          y < centralYEnd;

        tilesToCreate.push({
          x,
          y,
          terrainType: isCentral
            ? 'EMPTY'
            : Math.random() < 0.5
            ? 'BLOCKED_TREE'
            : 'BLOCKED_STONE',
          terrainState: 'IDLE',
        });
      }
    }

    await tx.villageTile.createMany({
      data: tilesToCreate.map((t) => ({
        ...t,
        villageId: village!.id, // village is guaranteed to exist here
      })),
    });
  });
}

/**
 * BUILD command
 */
export async function buildOnTile(
  userId: number,
  tileId: number,
  buildingType: VillageObjectType
) {
  return prisma.$transaction(async (tx) => {
    const user = await tx.user.findUniqueOrThrow({
      where: { id: userId },
    });

    const tile = await tx.villageTile.findUniqueOrThrow({
      where: { id: tileId },
      include: { village: true },
    });

    if (tile.village.userId !== userId) {
      throw createError({ statusCode: 403, statusMessage: 'Not your tile' });
    }

    if (tile.terrainType !== 'EMPTY' || tile.object) {
      throw createError({ statusCode: 400, statusMessage: 'Tile is not empty' });
    }

    const cost = COSTS.BUILD[buildingType];
    if (user.coins < cost) {
      throw createError({ statusCode: 400, statusMessage: 'Not enough coins' });
    }

    if (PRODUCING_BUILDINGS.includes(buildingType as any)) {
      const objects = await tx.villageObject.findMany({
        where: { villageId: tile.villageId },
      });

      const houses = objects.filter(o => o.type === 'HOUSE').length;
      const producing = objects.filter(o =>
        PRODUCING_BUILDINGS.includes(o.type as any)
      ).length;

      if (producing >= houses) {
        throw createError({
          statusCode: 400,
          statusMessage: 'Not enough workers',
        });
      }
    }

    await tx.user.update({
      where: { id: userId },
      data: { coins: { decrement: cost } },
    });

    await tx.villageObject.create({
      data: {
        type: buildingType,
        villageId: tile.villageId,
        tileId: tile.id,
      },
    });

    await tx.villageEvent.create({
      data: {
        villageId: tile.villageId,
        type: `BUILD_${buildingType}`,
        message: `Построено ${buildingType} на (${tile.x}, ${tile.y})`,
        tileX: tile.x,
        tileY: tile.y,
        coins: -cost,
        exp: 0,
      },
    });
  });
}

/* =========================
   INTERNAL HELPERS
========================= */

function fetchVillage(userId: number) {
  return prisma.village.findUnique({
    where: { userId },
    include: {
      user: true,
      tiles: { include: { object: true } },
      objects: { include: { tile: true } },
    },
  });
}

/* =========================
   DAY-BASED LOGIC
========================= */

async function processFinishedClearing(
  tx: Prisma.TransactionClient,
  village: FullVillage,
  today: string
) {
  const finishedTiles = village.tiles.filter(
    t =>
      t.terrainState === 'CLEARING' &&
      isBeforeDay(t.clearingStartedDay, today)
  );

  if (!finishedTiles.length) return;

  const baseReward = REWARDS.VILLAGE.CLEARING;
  const totalBaseReward = {
    coins: baseReward.coins * finishedTiles.length,
    exp: baseReward.exp * finishedTiles.length,
  };

  const finalReward = applyStreakMultiplier(totalBaseReward, village.user.dailyStreak);

  await tx.user.update({
    where: { id: village.user.id },
    data: {
      coins: { increment: finalReward.coins },
      exp: { increment: finalReward.exp },
    },
  });

  await tx.villageTile.updateMany({
    where: { id: { in: finishedTiles.map(t => t.id) } },
    data: {
      terrainType: 'EMPTY',
      terrainState: 'IDLE',
      clearingStartedDay: null,
    },
  });

  const streakMultiplier = village.user.dailyStreak > 1 ? village.user.dailyStreak : 0;
  let streakBonusText = '';
  if (streakMultiplier > 1) {
    streakBonusText = ` Ваша серия визитов (${streakMultiplier}) увеличила награду.`;
  }
  
  const events = finishedTiles.map(t => {
    const tileReward = applyStreakMultiplier(baseReward, village.user.dailyStreak);
    return {
      villageId: village.id,
      type: t.terrainType === 'BLOCKED_TREE' ? 'CLEAR_TREE' : 'CLEAR_STONE',
      message: `Участок (${t.x}, ${t.y}) расчищен.` + streakBonusText,
      tileX: t.x,
      tileY: t.y,
      coins: tileReward.coins,
      exp: tileReward.exp,
    };
  });

  await tx.villageEvent.createMany({
    data: events,
  });
}

async function processFieldExp(
  tx: Prisma.TransactionClient,
  village: FullVillage,
  today: string
) {
  const fieldsNeedingUpdate = village.objects.filter(
    (o) => o.type === 'FIELD' && isBeforeDay(o.lastExpDay, today)
  );

  if (!fieldsNeedingUpdate.length) return;

  const wells = village.objects.filter(o => o.type === 'WELL');
  let totalBaseExpGained = 0;
  const eventsToCreate: any[] = [];
  
  const streakMultiplierValue = village.user.dailyStreak > 1 ? village.user.dailyStreak : 1;
  let streakBonusText = '';
  if (streakMultiplierValue > 1) {
    streakBonusText = ` Ваша серия визитов (${streakMultiplierValue}) увеличила награду.`;
  }

  for (const field of fieldsNeedingUpdate) {
    const daysMissed = field.lastExpDay ? daysSince(field.lastExpDay, today) : 1;
    let expGainedForField = daysMissed * REWARDS.VILLAGE.FIELD_EXP.BASE;

    const isNearWell = wells.some(well => 
      Math.abs(well.tile.x - field.tile.x) <= 1 && 
      Math.abs(well.tile.y - field.tile.y) <= 1 &&
      (well.tile.x !== field.tile.x || well.tile.y !== field.tile.y)
    );

    let wellBonusText = '';
    if (isNearWell) {
      expGainedForField *= REWARDS.VILLAGE.FIELD_EXP.WELL_MULTIPLIER;
      wellBonusText = ' Рядом с колодцем урожай удвоился!';
    }

    totalBaseExpGained += expGainedForField;

    const finalExpForField = applyStreakMultiplier({ coins: 0, exp: expGainedForField }, village.user.dailyStreak);

    for (let i = 0; i < daysMissed; i++) {
      eventsToCreate.push({
        villageId: village.id,
        type: 'FIELD_EXP',
        message: `Поле (${field.tile.x}, ${field.tile.y}) принесло опыт.` + wellBonusText + streakBonusText,
        tileX: field.tile.x,
        tileY: field.tile.y,
        coins: 0,
        exp: finalExpForField.exp / daysMissed,
      });
    }
  }

  const finalExp = applyStreakMultiplier({ coins: 0, exp: totalBaseExpGained }, village.user.dailyStreak);

  if (totalBaseExpGained > 0) {
    await tx.user.update({
      where: { id: village.user.id },
      data: { exp: { increment: finalExp.exp } },
    });
  }

  await tx.villageObject.updateMany({
    where: { id: { in: fieldsNeedingUpdate.map((f) => f.id) } },
    data: { lastExpDay: today },
  });

  if (eventsToCreate.length > 0) {
    await tx.villageEvent.createMany({
      data: eventsToCreate,
    });
  }
}

async function autoStartClearing(
  tx: Prisma.TransactionClient,
  village: FullVillage,
  today: string
) {
  const lumberjacks = village.objects.filter(o => o.type === 'LUMBERJACK').length;
  const quarries = village.objects.filter(o => o.type === 'QUARRY').length;

  const busyTrees = village.tiles.filter(
    t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'CLEARING'
  ).length;

  const busyStones = village.tiles.filter(
    t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'CLEARING'
  ).length;

  const tilesToStart = [
    ...village.tiles
      .filter(t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'IDLE')
      .slice(0, lumberjacks - busyTrees),
    ...village.tiles
      .filter(
        t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'IDLE'
      )
      .slice(0, quarries - busyStones),
  ];

  if (!tilesToStart.length) return;

  await tx.villageTile.updateMany({
    where: { id: { in: tilesToStart.map(t => t.id) } },
    data: {
      terrainState: 'CLEARING',
      clearingStartedDay: today,
    },
  });
}
