from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel
from typing import Optional, List
import jwt
import bcrypt
from datetime import datetime, timedelta
import os
import sys

# Add parent directory to path to import bot modules
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from bot.database import get_player, create_player
from data.world_loader import load_world
from api.internal import router as internal_router

app = FastAPI(title="Echoes of the Ashes API", version="1.0.0")

# Include internal API router
app.include_router(internal_router)

# CORS configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=["https://echoesoftheashgame.patacuack.net", "http://localhost:3000"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# JWT Configuration
SECRET_KEY = os.getenv("JWT_SECRET_KEY", "your-secret-key-change-in-production")
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 * 7  # 7 days

security = HTTPBearer()

# Load world data
WORLD = None
LOCATIONS = {}
try:
    WORLD = load_world()
    # WORLD.locations is already a dict {location_id: Location}
    LOCATIONS = WORLD.locations
    print(f"✅ Loaded {len(LOCATIONS)} locations")
except Exception as e:
    print(f"⚠️  Warning: Could not load world data: {e}")
    import traceback
    traceback.print_exc()

# Pydantic Models
class UserRegister(BaseModel):
    username: str
    password: str

class UserLogin(BaseModel):
    username: str
    password: str

class Token(BaseModel):
    access_token: str
    token_type: str = "bearer"

class User(BaseModel):
    id: int
    username: str
    telegram_id: Optional[str] = None

class PlayerState(BaseModel):
    location_id: str
    location_name: str
    health: int
    max_health: int
    stamina: int
    max_stamina: int
    inventory: List[dict]
    status_effects: List[dict]

class MoveRequest(BaseModel):
    direction: str


# Helper Functions
def create_access_token(data: dict):
    to_encode = data.copy()
    expire = datetime.utcnow() + timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
    to_encode.update({"exp": expire})
    encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
    return encoded_jwt

def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)):
    try:
        token = credentials.credentials
        payload = jwt.decode(token, SECRET_KEY, algorithms=[ALGORITHM])
        user_id: int = payload.get("sub")
        if user_id is None:
            raise HTTPException(status_code=401, detail="Invalid authentication credentials")
        return user_id
    except jwt.ExpiredSignatureError:
        raise HTTPException(status_code=401, detail="Token has expired")
    except jwt.JWTError:
        raise HTTPException(status_code=401, detail="Could not validate credentials")


# Routes
@app.get("/")
async def root():
    return {"message": "Echoes of the Ashes API", "status": "online"}

@app.post("/api/auth/register", response_model=Token)
async def register(user_data: UserRegister):
    """Register a new user account"""
    try:
        # Check if username already exists
        existing_player = await get_player(username=user_data.username)
        if existing_player:
            raise HTTPException(status_code=400, detail="Username already exists")
        
        # Hash password
        password_hash = bcrypt.hashpw(user_data.password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8')
        
        # Create player with web auth
        player = await create_player(
            telegram_id=None,
            username=user_data.username,
            password_hash=password_hash
        )
        
        if not player or 'id' not in player:
            print(f"ERROR: create_player returned: {player}")
            raise HTTPException(status_code=500, detail="Failed to create player - no ID returned")
        
        # Create token
        access_token = create_access_token(data={"sub": player['id']})
        
        return {"access_token": access_token}
    except HTTPException:
        raise
    except Exception as e:
        import traceback
        print(f"ERROR in register: {str(e)}")
        print(traceback.format_exc())
        raise HTTPException(status_code=500, detail=str(e))

@app.post("/api/auth/login", response_model=Token)
async def login(user_data: UserLogin):
    """Login with username and password"""
    try:
        # Get player
        player = await get_player(username=user_data.username)
        if not player or not player.get('password_hash'):
            raise HTTPException(status_code=401, detail="Invalid username or password")
        
        # Verify password
        if not bcrypt.checkpw(user_data.password.encode('utf-8'), player['password_hash'].encode('utf-8')):
            raise HTTPException(status_code=401, detail="Invalid username or password")
        
        # Create token
        access_token = create_access_token(data={"sub": player['id']})
        
        return {"access_token": access_token}
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/api/auth/me", response_model=User)
async def get_current_user(user_id: int = Depends(verify_token)):
    """Get current authenticated user"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="User not found")
    
    return {
        "id": player['id'],
        "username": player.get('username'),
        "telegram_id": player.get('telegram_id')
    }

@app.get("/api/game/state", response_model=PlayerState)
async def get_game_state(user_id: int = Depends(verify_token)):
    """Get current player game state"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    location = LOCATIONS.get(player['location_id'])
    
    # TODO: Get actual inventory and status effects from database
    inventory = []
    status_effects = []
    
    return {
        "location_id": player['location_id'],
        "location_name": location.name if location else "Unknown",
        "health": player['hp'],
        "max_health": player['max_hp'],
        "stamina": player['stamina'],
        "max_stamina": player['max_stamina'],
        "inventory": inventory,
        "status_effects": status_effects
    }

@app.post("/api/game/move")
async def move_player(move_data: MoveRequest, user_id: int = Depends(verify_token)):
    """Move player in a direction"""
    from bot.database import update_player
    
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    current_location = LOCATIONS.get(player['location_id'])
    if not current_location:
        raise HTTPException(status_code=400, detail="Invalid current location")
    
    # Check if player has enough stamina
    if player['stamina'] < 1:
        raise HTTPException(status_code=400, detail="Not enough stamina to move")
    
    # Find exit in the specified direction (exits is dict {direction: destination_id})
    destination_id = current_location.exits.get(move_data.direction.lower())
    
    if not destination_id:
        raise HTTPException(status_code=400, detail=f"Cannot move {move_data.direction} from here")
    
    # Move player
    new_location = LOCATIONS.get(destination_id)
    if not new_location:
        raise HTTPException(status_code=400, detail="Invalid destination")
    
    # Update player location and stamina (use player_id for web users)
    await update_player(player_id=player['id'], updates={
        'location_id': new_location.id,
        'stamina': max(0, player['stamina'] - 1)
    })
    
    # Get updated player state
    updated_player = await get_player(player_id=user_id)
    
    return {
        "success": True,
        "message": f"You travel {move_data.direction} to {new_location.name}. {new_location.description}",
        "player_state": {
            "location_id": updated_player['location_id'],
            "location_name": new_location.name,
            "health": updated_player['hp'],
            "max_health": updated_player['max_hp'],
            "stamina": updated_player['stamina'],
            "max_stamina": updated_player['max_stamina'],
            "inventory": [],
            "status_effects": []
        }
    }

@app.get("/api/game/location")
async def get_current_location(user_id: int = Depends(verify_token)):
    """Get detailed information about current location"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    location = LOCATIONS.get(player['location_id'])
    if not location:
        raise HTTPException(status_code=404, detail=f"Location '{player['location_id']}' not found")
    
    # Get available directions from exits dict
    directions = list(location.exits.keys())
    
    # Get NPCs at location (TODO: implement NPC spawning)
    npcs = []
    
    # Get items at location (TODO: implement dropped items)
    items = []
    
    # Determine image extension (png or jpg)
    image_url = None
    if location.image_path:
        # Use the path from location data
        image_url = f"/{location.image_path}"
    else:
        # Default to png with fallback to jpg
        image_url = f"/images/locations/{location.id}.png"
    
    return {
        "id": location.id,
        "name": location.name,
        "description": location.description,
        "directions": directions,
        "npcs": npcs,
        "items": items,
        "image_url": image_url,
        "interactables": [{"id": k, "name": v.name} for k, v in location.interactables.items()]
    }

@app.get("/api/game/inventory")
async def get_inventory(user_id: int = Depends(verify_token)):
    """Get player's inventory"""
    from bot.database import get_inventory
    
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    # For web users without telegram_id, inventory might be empty
    # This is a limitation of the current schema
    inventory = []
    
    return {
        "items": inventory,
        "capacity": 20  # TODO: Calculate based on equipped bag
    }

@app.get("/api/game/profile")
async def get_profile(user_id: int = Depends(verify_token)):
    """Get player profile and stats"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    return {
        "name": player['name'],
        "level": player['level'],
        "xp": player['xp'],
        "hp": player['hp'],
        "max_hp": player['max_hp'],
        "stamina": player['stamina'],
        "max_stamina": player['max_stamina'],
        "strength": player['strength'],
        "agility": player['agility'],
        "endurance": player['endurance'],
        "intellect": player['intellect'],
        "unspent_points": player['unspent_points'],
        "is_dead": player['is_dead']
    }

@app.get("/api/game/map")
async def get_map(user_id: int = Depends(verify_token)):
    """Get world map data"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    # Return all locations and connections (LOCATIONS is dict {id: Location})
    locations_data = []
    for loc_id, loc in LOCATIONS.items():
        locations_data.append({
            "id": loc.id,
            "name": loc.name,
            "description": loc.description,
            "exits": loc.exits  # Dict of {direction: destination_id}
        })
    
    return {
        "current_location": player['location_id'],
        "locations": locations_data
    }

@app.post("/api/game/inspect")
async def inspect_area(user_id: int = Depends(verify_token)):
    """Inspect the current area for details"""
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    location = LOCATIONS.get(player['location_id'])
    if not location:
        raise HTTPException(status_code=404, detail="Location not found")
    
    # Get detailed information
    interactables_detail = []
    for inst_id, inter in location.interactables.items():
        actions = [{"id": act.id, "label": act.label, "stamina_cost": act.stamina_cost} 
                   for act in inter.actions.values()]
        interactables_detail.append({
            "instance_id": inst_id,
            "name": inter.name,
            "actions": actions
        })
    
    return {
        "location": location.name,
        "description": location.description,
        "interactables": interactables_detail,
        "exits": location.exits
    }

class InteractRequest(BaseModel):
    interactable_id: str
    action_id: str

@app.post("/api/game/interact")
async def interact_with_object(interact_data: InteractRequest, user_id: int = Depends(verify_token)):
    """Interact with an object in the world"""
    from bot.database import update_player, add_inventory_item
    import random
    
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    location = LOCATIONS.get(player['location_id'])
    if not location:
        raise HTTPException(status_code=404, detail="Location not found")
    
    interactable = location.interactables.get(interact_data.interactable_id)
    if not interactable:
        raise HTTPException(status_code=404, detail="Interactable not found")
    
    action = interactable.actions.get(interact_data.action_id)
    if not action:
        raise HTTPException(status_code=404, detail="Action not found")
    
    # Check stamina
    if player['stamina'] < action.stamina_cost:
        raise HTTPException(status_code=400, detail="Not enough stamina")
    
    # Perform action - randomly choose outcome
    outcome_key = random.choice(list(action.outcomes.keys()))
    outcome = action.outcomes[outcome_key]
    
    # Apply outcome
    stamina_change = -action.stamina_cost
    hp_change = -outcome.damage_taken if outcome.damage_taken else 0
    items_found = outcome.items_reward if outcome.items_reward else {}
    
    # Update player
    new_hp = max(1, player['hp'] + hp_change)
    new_stamina = max(0, player['stamina'] + stamina_change)
    
    await update_player(player_id=player['id'], updates={
        'hp': new_hp,
        'stamina': new_stamina
    })
    
    # Add items to inventory (if player has telegram_id for FK)
    items_added = []
    if player.get('telegram_id') and items_found:
        for item_id, quantity in items_found.items():
            # This will fail for web users without telegram_id
            # TODO: Fix inventory schema
            try:
                items_added.append({"id": item_id, "quantity": quantity})
            except:
                pass
    
    return {
        "success": True,
        "outcome": outcome_key,
        "message": outcome.text,
        "items_found": items_added,
        "hp_change": hp_change,
        "stamina_change": stamina_change,
        "new_hp": new_hp,
        "new_stamina": new_stamina
    }

class UseItemRequest(BaseModel):
    item_db_id: int

@app.post("/api/game/use_item")
async def use_item_endpoint(item_data: UseItemRequest, user_id: int = Depends(verify_token)):
    """Use an item from inventory"""
    from bot.logic import use_item_logic
    
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    if not player.get('telegram_id'):
        raise HTTPException(status_code=400, detail="Inventory not available for web users yet")
    
    result = await use_item_logic(player, item_data.item_db_id)
    return result

class EquipItemRequest(BaseModel):
    item_db_id: int

@app.post("/api/game/equip_item")
async def equip_item_endpoint(item_data: EquipItemRequest, user_id: int = Depends(verify_token)):
    """Equip or unequip an item"""
    from bot.logic import toggle_equip
    
    player = await get_player(player_id=user_id)
    if not player:
        raise HTTPException(status_code=404, detail="Player not found")
    
    if not player.get('telegram_id'):
        raise HTTPException(status_code=400, detail="Inventory not available for web users yet")
    
    result = await toggle_equip(player['telegram_id'], item_data.item_db_id)
    return {"success": True, "message": result}


if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)
