import { PrismaClient, User, Village, VillageTile, VillageObject, Prisma } from '@prisma/client';
import { COSTS, REWARDS } from '../utils/economy';

const prisma = new PrismaClient();

export const VILLAGE_WIDTH = 5;
export const VILLAGE_HEIGHT = 7;
const CLEANING_TIME = 24 * 60 * 60 * 1000; // 24 hours

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

// Helper to get the start of a given date for daily EXP checks
const getStartOfDay = (date: Date) => {
  const d = new Date(date);
  d.setUTCHours(0, 0, 0, 0); // Use UTC for calendar day consistency
  return d;
};

/**
 * Generates the initial village for a new user atomically.
 */
export async function generateVillageForUser(user: User) {
  const existingVillage = await prisma.village.findUnique({
    where: { userId: user.id },
  });

  if (existingVillage) {
    return;
  }

  const tilesToCreate: Omit<VillageTile, 'id' | 'clearingStartedAt' | 'villageId'>[] = [];
  const central_x_start = 1;
  const central_x_end = 4;
  const central_y_start = 2;
  const central_y_end = 5;

  for (let y = 0; y < VILLAGE_HEIGHT; y++) {
    for (let x = 0; x < VILLAGE_WIDTH; x++) {
      const isCentral = x >= central_x_start && x < central_x_end && y >= central_y_start && y < central_y_end;
      const terrainType = isCentral
        ? 'EMPTY'
        : Math.random() < 0.5 ? 'BLOCKED_TREE' : 'BLOCKED_STONE';
      
      tilesToCreate.push({
        x,
        y,
        terrainType,
        terrainState: 'IDLE',
      });
    }
  }
  
  // FIX: Wrap village generation in a single transaction for atomicity.
  await prisma.$transaction(async (tx) => {
    const village = await tx.village.create({
      data: {
        userId: user.id,
      },
    });

    await tx.user.update({
      where: { id: user.id },
      data: {
        coins: 10,
        exp: 0,
      },
    });

    await tx.villageTile.createMany({
      data: tilesToCreate.map(tile => ({ ...tile, villageId: village.id })),
    });
  });
}

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

/**
 * Gets the full, updated state of a user's village, calculating all time-based progression.
 */
export async function getVillageState(userId: number): Promise<FullVillage> {
  const now = new Date();
  const { applyStreakMultiplier } = await import('../utils/streak');
  const today = getStartOfDay(now);
  
  // --- Step 1: Initial Snapshot ---
  let villageSnapshot = await prisma.village.findUnique({
    where: { userId },
    include: {
      user: true,
      tiles: { include: { object: true } },
      objects: { include: { tile: true } },
    },
  });

  if (!villageSnapshot) {
    throw createError({ statusCode: 404, statusMessage: 'Village not found' });
  }

  const userForStreak = villageSnapshot.user;

  // --- Step 2: Terrain Cleaning Completion ---
  const finishedClearingTiles = villageSnapshot.tiles.filter(
    t => t.terrainState === 'CLEARING' && t.clearingStartedAt && getStartOfDay(t.clearingStartedAt) < today
  );

  if (finishedClearingTiles.length > 0) {
    // Apply streak multiplier to clearing rewards
    const baseClearingReward = REWARDS.VILLAGE.CLEARING;
    const finalClearingReward = applyStreakMultiplier(baseClearingReward, userForStreak.dailyStreak);

    const totalCoins = finishedClearingTiles.length * finalClearingReward.coins;
    const totalExp = finishedClearingTiles.length * finalClearingReward.exp;

    await prisma.$transaction(async (tx) => {
      // 1. Update user totals
      await tx.user.update({
        where: { id: userId },
        data: {
          coins: { increment: totalCoins },
          exp: { increment: totalExp },
        },
      });

      // 2. Update all the tiles
      await tx.villageTile.updateMany({
          where: { id: { in: finishedClearingTiles.map(t => t.id) } },
          data: { terrainType: 'EMPTY', terrainState: 'IDLE', clearingStartedAt: null },
      });

      // 3. Create an event for each completed tile with the final reward
      const multiplier = userForStreak.dailyStreak;
      let streakBonusText = '';
      if (multiplier === 2) {
        streakBonusText = ' Ваша серия визитов (x2) удвоила награду!';
      } else if (multiplier >= 3) {
        streakBonusText = ' Ваша серия визитов (x3) утроила награду!';
      }

      for (const tile of finishedClearingTiles) {
        const resourceName = tile.terrainType === 'BLOCKED_TREE' ? 'дерево' : 'камень';
        const actionText = tile.terrainType === 'BLOCKED_TREE' ? 'Лесоруб расчистил участок' : 'Каменотес раздробил валун';
        
        await tx.villageEvent.create({
          data: {
            villageId: villageSnapshot.id,
            type: tile.terrainType === 'BLOCKED_TREE' ? 'CLEAR_TREE' : 'CLEAR_STONE',
            message: `${actionText}, принеся вам ${finalClearingReward.coins} монет и ${finalClearingReward.exp} опыта.${streakBonusText}`,
            tileX: tile.x,
            tileY: tile.y,
            coins: finalClearingReward.coins,
            exp: finalClearingReward.exp,
          }
        });
      }
    });
  }
  
  // --- Step 3: Refetch for next logic step ---
  villageSnapshot = await prisma.village.findUnique({ where: { userId }, include: { user: true, tiles: { include: { object: true } }, objects: { include: { tile: true } } } })!;


  const fieldsForExp = villageSnapshot.objects.filter(
    obj => obj.type === 'FIELD' && (!obj.lastExpAt || getStartOfDay(obj.lastExpAt) < today)
  );

  if (fieldsForExp.length > 0) {
    const wellPositions = new Set(villageSnapshot.objects.filter(obj => obj.type === 'WELL').map(w => `${w.tile.x},${w.tile.y}`));
    let totalExpFromFields = 0;
    const eventsToCreate = [];

    for (const field of fieldsForExp) {
      // First, calculate base EXP with existing game logic (well bonus)
      let baseFieldExp = REWARDS.VILLAGE.FIELD_EXP.BASE;
      if (wellPositions.has(`${field.tile.x},${field.tile.y - 1}`) || wellPositions.has(`${field.tile.x},${field.tile.y + 1}`) || wellPositions.has(`${field.tile.x - 1},${field.tile.y}`) || wellPositions.has(`${field.tile.x + 1},${field.tile.y}`)) {
        baseFieldExp *= REWARDS.VILLAGE.FIELD_EXP.WELL_MULTIPLIER;
      }
      
      // Now, apply the daily streak multiplier
      const finalFieldExp = applyStreakMultiplier({ coins: 0, exp: baseFieldExp }, userForStreak.dailyStreak).exp;

      totalExpFromFields += finalFieldExp;

      const multiplier = userForStreak.dailyStreak;
      let streakBonusText = '';
      if (multiplier === 2) {
        streakBonusText = ' Ваша серия визитов (x2) удвоила урожай опыта!';
      } else if (multiplier >= 3) {
        streakBonusText = ' Ваша серия визитов (x3) утроила урожай опыта!';
      }

      eventsToCreate.push({
        villageId: villageSnapshot.id,
        type: 'FIELD_EXP',
        message: `Поле (${field.tile.x}, ${field.tile.y}) плодоносит, принося вам ${finalFieldExp} опыта.${streakBonusText}`,
        tileX: field.tile.x,
        tileY: field.tile.y,
        coins: 0,
        exp: finalFieldExp,
      });
    }
    
    await prisma.$transaction([
      prisma.user.update({ where: { id: userId }, data: { exp: { increment: totalExpFromFields } } }),
      prisma.villageObject.updateMany({ where: { id: { in: fieldsForExp.map(f => f.id) } }, data: { lastExpAt: today } }),
      prisma.villageEvent.createMany({ data: eventsToCreate }),
    ]);
  }
  
  // --- Step 5: Refetch for next logic step ---
  villageSnapshot = await prisma.village.findUnique({ where: { userId }, include: { user: true, tiles: { include: { object: true } }, objects: { include: { tile: true } } } })!;

  // --- Step 6: Auto-start Terrain Cleaning ---
  const lumberjackCount = villageSnapshot.objects.filter(o => o.type === 'LUMBERJACK').length;
  const quarryCount = villageSnapshot.objects.filter(o => o.type === 'QUARRY').length;

  const clearingTreesCount = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'CLEARING').length;
  const clearingStonesCount = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'CLEARING').length;

  const freeLumberjacks = lumberjackCount - clearingTreesCount;
  const freeQuarries = quarryCount - clearingStonesCount;

  const tileIdsToClear = new Set<number>();

  if (freeLumberjacks > 0) {
    const idleTrees = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_TREE' && t.terrainState === 'IDLE');
    if (idleTrees.length > 0) {
        // For simplicity, just take the first N available trees. A more complex distance-based heuristic could go here.
        idleTrees.slice(0, freeLumberjacks).forEach(t => tileIdsToClear.add(t.id));
    }
  }

  if (freeQuarries > 0) {
    const idleStones = villageSnapshot.tiles.filter(t => t.terrainType === 'BLOCKED_STONE' && t.terrainState === 'IDLE');
    if (idleStones.length > 0) {
        // For simplicity, just take the first N available stones.
        idleStones.slice(0, freeQuarries).forEach(t => tileIdsToClear.add(t.id));
    }
  }
  
  if (tileIdsToClear.size > 0) {
    await prisma.villageTile.updateMany({
      where: { id: { in: Array.from(tileIdsToClear) } },
      data: { terrainState: 'CLEARING', clearingStartedAt: getStartOfDay(now) },
    });

    // Refetch state after starting new clearings
    villageSnapshot = await prisma.village.findUnique({ where: { userId }, include: { user: true, tiles: { include: { object: true } }, objects: { include: { tile: true } } } })!;
  }

  // --- Step 7: Final Fetch & Action Enrichment ---
  const finalVillageState = await prisma.village.findUnique({
    where: { userId },
    include: {
      user: true,
      tiles: { include: { object: true }, orderBy: [{ y: 'asc' }, { x: 'asc' }] },
      objects: { include: { tile: true } },
    },
  });

  if (!finalVillageState) {
    throw createError({ statusCode: 404, statusMessage: 'Village not found post-update' });
  }

  // --- Step 8: Enrich tiles with available actions ---
  const { user } = finalVillageState;
  const hasLumberjack = finalVillageState.objects.some(o => o.type === 'LUMBERJACK');
  const hasQuarry = finalVillageState.objects.some(o => o.type === 'QUARRY');
  
  const housesCount = finalVillageState.objects.filter(o => o.type === 'HOUSE').length;
  const producingCount = finalVillageState.objects.filter(o => PRODUCING_BUILDINGS.includes(o.type)).length;
  const freeWorkers = housesCount - producingCount;

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

    // Action: BUILD
    if (tile.terrainType === 'EMPTY' && !tile.object) {
      const buildableObjectTypes = ['HOUSE', 'FIELD', 'LUMBERJACK', 'QUARRY', 'WELL'];
      const buildActions = buildableObjectTypes.map(buildingType => {
        const cost = COSTS.BUILD[buildingType];
        const isProducing = PRODUCING_BUILDINGS.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);
    }
    
    if (tile.object) {
      // MOVE and REMOVE actions have been removed as per the refactor request.
    }

    return { ...tile, availableActions };
  });

  return { ...finalVillageState, tiles: tilesWithActions } as any;
}

// --- Action Service Functions ---

export async function buildOnTile(userId: number, tileId: number, buildingType: string) {
  const { VillageObjectType } = await import('@prisma/client');
  const validBuildingTypes = Object.keys(VillageObjectType);
  if (!validBuildingTypes.includes(buildingType)) {
    throw createError({ statusCode: 400, statusMessage: `Invalid building type: ${buildingType}` });
  }

  return prisma.$transaction(async (tx) => {
    // 1. Fetch all necessary data
    const user = await tx.user.findUniqueOrThrow({ where: { id: userId } });
    const tile = await tx.villageTile.findUniqueOrThrow({ where: { id: tileId }, include: { village: true } });
    
    // Ownership check
    if (tile.village.userId !== userId) {
      throw createError({ statusCode: 403, statusMessage: "You don't own this tile" });
    }

    // Business logic validation
    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)) {
        const villageObjects = await tx.villageObject.findMany({ where: { villageId: tile.villageId } });
        const housesCount = villageObjects.filter(o => o.type === 'HOUSE').length;
        const producingCount = villageObjects.filter(o => PRODUCING_BUILDINGS.includes(o.type)).length;
        if (producingCount >= housesCount) {
            throw createError({ statusCode: 400, statusMessage: 'Not enough workers (houses)' });
        }
    }

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

    await tx.villageObject.create({
      data: {
        type: buildingType as keyof typeof VillageObjectType,
        villageId: tile.villageId,
        tileId: tileId,
      },
    });

    await tx.villageEvent.create({
      data: {
          villageId: tile.villageId,
          type: `BUILD_${buildingType}`,
          message: `Built a ${buildingType} at (${tile.x}, ${tile.y})`,
          tileX: tile.x,
          tileY: tile.y,
          coins: -cost,
          exp: 0,
      },
    });
      });
  }

  