from fastapi import FastAPI, APIRouter, HTTPException, Request, Response, Depends
from dotenv import load_dotenv
from starlette.middleware.cors import CORSMiddleware
from motor.motor_asyncio import AsyncIOMotorClient
import os
import io
import base64
import logging
import uuid
import qrcode
import bcrypt
import jwt
import asyncio
import resend
from pathlib import Path
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import datetime, timezone, timedelta

from emergentintegrations.payments.stripe.checkout import (
    StripeCheckout,
    CheckoutSessionResponse,
    CheckoutStatusResponse,
    CheckoutSessionRequest,
)

ROOT_DIR = Path(__file__).parent
load_dotenv(ROOT_DIR / '.env')

mongo_url = os.environ['MONGO_URL']
client = AsyncIOMotorClient(mongo_url)
db = client[os.environ['DB_NAME']]

STRIPE_API_KEY = os.environ['STRIPE_API_KEY']

app = FastAPI()
api_router = APIRouter(prefix="/api")

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


def now_iso():
    return datetime.now(timezone.utc).isoformat()


def make_qr(data: str) -> str:
    qr = qrcode.QRCode(box_size=8, border=2)
    qr.add_data(data)
    qr.make(fit=True)
    img = qr.make_image(fill_color="black", back_color="white")
    buf = io.BytesIO()
    img.save(buf, format="PNG")
    return base64.b64encode(buf.getvalue()).decode()


# ---------------- Auth Helpers ----------------
def hash_password(password: str) -> str:
    return bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")


def verify_password(plain: str, hashed: str) -> bool:
    return bcrypt.checkpw(plain.encode("utf-8"), hashed.encode("utf-8"))


async def get_current_admin(request: Request) -> dict:
    token = request.cookies.get("access_token")
    if not token:
        auth_header = request.headers.get("Authorization", "")
        if auth_header.startswith("Bearer "):
            token = auth_header[7:]
    if not token:
        raise HTTPException(401, "Oturum bulunamadı")
    try:
        payload = jwt.decode(token, JWT_SECRET, algorithms=[JWT_ALGORITHM])
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Oturum süresi doldu")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Geçersiz oturum")
    user = await db.users.find_one({"id": payload.get("sub")}, {"_id": 0, "password_hash": 0})
    if not user or user.get("role") != "admin":
        raise HTTPException(401, "Yetkisiz erişim")
    return user


# ---------------- Models ----------------
class ContactCreate(BaseModel):
    name: str
    email: str
    message: str


class NewsletterCreate(BaseModel):
    email: str


class DiscountValidate(BaseModel):
    code: str


class CheckoutCreate(BaseModel):
    play_id: str
    showtime_id: str
    category: str
    quantity: int = Field(ge=1, le=10)
    discount_code: Optional[str] = None
    customer_name: str
    customer_email: str
    origin_url: str


DISCOUNT_CODES = {"TIYATRO10": 10, "PERDE20": 20}
CATEGORY_LABELS = {"vip": "VIP", "normal": "Normal", "balkon": "Balkon"}


class LoginRequest(BaseModel):
    email: str
    password: str


class ShowtimeInput(BaseModel):
    id: Optional[str] = None
    date: str
    time: str
    venue: str


class PlayInput(BaseModel):
    title: str
    genre: str
    poster: str
    summary: str
    director: str
    duration: int
    age_limit: str
    venue: str
    prices: dict
    cast: List[str]
    featured: bool = False
    is_new: bool = False
    trailer: str = ""
    showtimes: List[ShowtimeInput] = []


# ---------------- Seed Data ----------------
def gen_showtimes(offsets, time_str, venue):
    base = datetime.now(timezone.utc)
    return [
        {
            "id": str(uuid.uuid4()),
            "date": (base + timedelta(days=o)).strftime("%Y-%m-%d"),
            "time": time_str,
            "venue": venue,
        }
        for o in offsets
    ]


SEED_PLAYS = [
    {
        "id": str(uuid.uuid4()),
        "title": "Bir Yaz Gecesi Rüyası",
        "genre": "Komedi",
        "poster": "https://images.unsplash.com/photo-1507924538820-ede94a04019d?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Shakespeare'in büyülü ormanında aşk, kıskançlık ve perilerin oyunları iç içe geçiyor. Dört genç âşığın ve bir grup amatör oyuncunun bir gecede yaşadıkları, modern bir yorumla sahneye taşınıyor.",
        "director": "Deniz Aksoy",
        "duration": 120,
        "age_limit": "7+",
        "venue": "Ana Sahne",
        "prices": {"vip": 750.0, "normal": 450.0, "balkon": 300.0},
        "cast": ["Elif Yıldırım", "Mert Kaya", "Zeynep Arslan", "Can Demirci"],
        "featured": True,
        "is_new": False,
        "views": 12400,
        "showtimes": gen_showtimes([2, 6, 13, 20], "20:30", "Ana Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Hamlet",
        "genre": "Trajedi",
        "poster": "https://images.unsplash.com/photo-1503095396549-807759245b35?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Danimarka Prensi Hamlet'in intikam, ihanet ve varoluş sancısıyla örülü hikâyesi. 'Olmak ya da olmamak' sorusunun yankılandığı, tiyatro tarihinin en büyük trajedisi minimalist bir sahne tasarımıyla.",
        "director": "Kerem Soylu",
        "duration": 150,
        "age_limit": "13+",
        "venue": "Ana Sahne",
        "prices": {"vip": 850.0, "normal": 500.0, "balkon": 350.0},
        "cast": ["Baran Tekin", "Selin Koçer", "Mert Kaya", "Aslı Güner"],
        "featured": False,
        "is_new": False,
        "views": 9800,
        "showtimes": gen_showtimes([4, 11, 18, 25], "20:00", "Ana Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Sefiller",
        "genre": "Müzikal",
        "poster": "https://images.unsplash.com/photo-1514306191717-452ec28c7814?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Victor Hugo'nun ölümsüz romanından uyarlanan görkemli müzikal. Jean Valjean'ın vicdan ve adalet arasındaki yolculuğu, 30 kişilik kadro ve canlı orkestra eşliğinde.",
        "director": "Deniz Aksoy",
        "duration": 165,
        "age_limit": "10+",
        "venue": "Açık Hava Sahnesi",
        "prices": {"vip": 950.0, "normal": 600.0, "balkon": 400.0},
        "cast": ["Can Demirci", "Elif Yıldırım", "Baran Tekin", "Zeynep Arslan", "Selin Koçer"],
        "featured": False,
        "is_new": True,
        "views": 15200,
        "showtimes": gen_showtimes([3, 10, 17], "21:00", "Açık Hava Sahnesi"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Kürk Mantolu Madonna",
        "genre": "Dram",
        "poster": "https://images.unsplash.com/photo-1629474468919-64a55bbae8eb?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Sabahattin Ali'nin unutulmaz eseri sahnede. Raif Efendi'nin Berlin'de başlayan ve bir ömre yayılan sessiz aşkı, iki kişilik yoğun bir oyunla seyirciyle buluşuyor.",
        "director": "Aslı Güner",
        "duration": 95,
        "age_limit": "13+",
        "venue": "Stüdyo Sahne",
        "prices": {"vip": 650.0, "normal": 400.0, "balkon": 275.0},
        "cast": ["Mert Kaya", "Selin Koçer"],
        "featured": False,
        "is_new": True,
        "views": 7600,
        "showtimes": gen_showtimes([1, 8, 15, 22], "19:30", "Stüdyo Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Cimri",
        "genre": "Komedi",
        "poster": "https://images.pexels.com/photos/12838778/pexels-photo-12838778.jpeg?auto=compress&cs=tinysrgb&w=800",
        "summary": "Molière'in ölümsüz komedisi. Para hırsıyla gözü dönmüş Harpagon'un ailesiyle ve çevresiyle yaşadığı absürt kaos, kahkaha dolu bir tempoyla sahnede.",
        "director": "Kerem Soylu",
        "duration": 110,
        "age_limit": "7+",
        "venue": "Stüdyo Sahne",
        "prices": {"vip": 600.0, "normal": 375.0, "balkon": 250.0},
        "cast": ["Baran Tekin", "Zeynep Arslan", "Can Demirci"],
        "featured": False,
        "is_new": False,
        "views": 11100,
        "showtimes": gen_showtimes([5, 12, 19, 26], "20:30", "Stüdyo Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Martı",
        "genre": "Dram",
        "poster": "https://images.unsplash.com/photo-1615414047026-802692414b79?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Çehov'un başyapıtı; sanat, aşk ve hayal kırıklığı üzerine bir göl kenarı hikâyesi. Genç yazar Treplev ile oyuncu Nina'nın kesişen yazgıları çağdaş bir okumayla.",
        "director": "Aslı Güner",
        "duration": 130,
        "age_limit": "13+",
        "venue": "Ana Sahne",
        "prices": {"vip": 700.0, "normal": 425.0, "balkon": 300.0},
        "cast": ["Elif Yıldırım", "Mert Kaya", "Aslı Güner", "Can Demirci"],
        "featured": False,
        "is_new": False,
        "views": 6300,
        "showtimes": gen_showtimes([7, 14, 21, 28], "20:00", "Ana Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Romeo ve Juliet",
        "genre": "Trajedi",
        "poster": "https://images.unsplash.com/photo-1499364615650-ec38552f4f34?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Verona'nın iki düşman ailesinin çocukları arasında filizlenen yasak aşk. Shakespeare'in en çok sahnelenen trajedisi, açık hava sahnesinde yıldızların altında; canlı müzik ve dans koreografisiyle.",
        "director": "Deniz Aksoy",
        "duration": 140,
        "age_limit": "10+",
        "venue": "Açık Hava Sahnesi",
        "prices": {"vip": 800.0, "normal": 475.0, "balkon": 325.0},
        "cast": ["Zeynep Arslan", "Can Demirci", "Baran Tekin", "Elif Yıldırım"],
        "featured": False,
        "is_new": True,
        "views": 8900,
        "showtimes": gen_showtimes([6, 13, 20, 27], "21:00", "Açık Hava Sahnesi"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Godot'yu Beklerken",
        "genre": "Dram",
        "poster": "https://images.unsplash.com/photo-1547153760-18fc86324498?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Beckett'in absürt başyapıtı. Vladimir ve Estragon, hiç gelmeyecek Godot'yu beklerken varoluşun anlamını sorguluyor. İki kişilik, soluksuz bir bekleyiş; minimalist dekor, maksimum etki.",
        "director": "Kerem Soylu",
        "duration": 105,
        "age_limit": "13+",
        "venue": "Stüdyo Sahne",
        "prices": {"vip": 625.0, "normal": 390.0, "balkon": 260.0},
        "cast": ["Mert Kaya", "Baran Tekin"],
        "featured": False,
        "is_new": True,
        "views": 5400,
        "showtimes": gen_showtimes([3, 10, 17, 24], "19:00", "Stüdyo Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Keşanlı Ali Destanı",
        "genre": "Müzikal",
        "poster": "https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Haldun Taner'in epik müzikali. Sineklidağ'ın kabadayısı Keşanlı Ali'nin işlemediği bir cinayetin kahramanı oluşu; türküler, zeybekler ve keskin toplumsal hicivle Türk tiyatrosunun kilometre taşı.",
        "director": "Deniz Aksoy",
        "duration": 145,
        "age_limit": "10+",
        "venue": "Ana Sahne",
        "prices": {"vip": 900.0, "normal": 550.0, "balkon": 375.0},
        "cast": ["Can Demirci", "Zeynep Arslan", "Elif Yıldırım", "Mert Kaya", "Selin Koçer"],
        "featured": False,
        "is_new": False,
        "views": 13700,
        "showtimes": gen_showtimes([9, 16, 23, 30], "20:30", "Ana Sahne"),
        "trailer": "",
    },
    {
        "id": str(uuid.uuid4()),
        "title": "Bernarda Alba'nın Evi",
        "genre": "Dram",
        "poster": "https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?crop=entropy&cs=srgb&fm=jpg&q=85&w=800",
        "summary": "Lorca'nın kadınlar üzerine yazdığı son oyunu. Sekiz yıllık yas ilan eden Bernarda'nın evinde beş kız kardeşin bastırılmış tutkuları, kapalı kapılar ardında fırtınaya dönüşüyor. Tamamı kadın kadrosuyla.",
        "director": "Aslı Güner",
        "duration": 115,
        "age_limit": "13+",
        "venue": "Stüdyo Sahne",
        "prices": {"vip": 650.0, "normal": 400.0, "balkon": 275.0},
        "cast": ["Selin Koçer", "Elif Yıldırım", "Zeynep Arslan", "Aslı Güner"],
        "featured": False,
        "is_new": False,
        "views": 7100,
        "showtimes": gen_showtimes([5, 12, 19, 26], "20:00", "Stüdyo Sahne"),
        "trailer": "",
    },
]

SEED_TEAM = [
    {"id": str(uuid.uuid4()), "name": "Elif Yıldırım", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Konservatuvar mezunu. 12 yıldır sahnede; 20'den fazla oyunda başrol üstlendi. En İyi Kadın Oyuncu ödülü sahibi.", "plays": ["Bir Yaz Gecesi Rüyası", "Sefiller", "Martı"]},
    {"id": str(uuid.uuid4()), "name": "Mert Kaya", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1587397845856-e6cf49176c70?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Fiziksel tiyatro ve doğaçlama alanında uzman. Avrupa turnelerinde topluluğu temsil etti.", "plays": ["Bir Yaz Gecesi Rüyası", "Hamlet", "Kürk Mantolu Madonna", "Martı"]},
    {"id": str(uuid.uuid4()), "name": "Zeynep Arslan", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1438761681033-6461ffad8d80?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Müzikal tiyatro kökenli; şan ve modern dans eğitimi aldı. Genç kuşağın en parlak isimlerinden.", "plays": ["Bir Yaz Gecesi Rüyası", "Sefiller", "Cimri"]},
    {"id": str(uuid.uuid4()), "name": "Can Demirci", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1500648767791-00dcc994a43e?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Tiyatroya üniversite kulübünde başladı; karakter oyunculuğuyla tanınıyor. Aynı zamanda seslendirme sanatçısı.", "plays": ["Bir Yaz Gecesi Rüyası", "Sefiller", "Cimri", "Martı"]},
    {"id": str(uuid.uuid4()), "name": "Baran Tekin", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Klasik metinlerdeki güçlü yorumuyla bilinen deneyimli oyuncu. Hamlet rolüyle eleştirmen ödülü aldı.", "plays": ["Hamlet", "Sefiller", "Cimri"]},
    {"id": str(uuid.uuid4()), "name": "Selin Koçer", "role": "Oyuncu", "type": "cast", "photo": "https://images.unsplash.com/photo-1544005313-94ddf0286df2?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Psikolojik derinlikli rollerin aranan ismi. Kürk Mantolu Madonna'daki performansıyla büyük beğeni topladı.", "plays": ["Hamlet", "Sefiller", "Kürk Mantolu Madonna"]},
    {"id": str(uuid.uuid4()), "name": "Deniz Aksoy", "role": "Yönetmen", "type": "crew", "photo": "https://images.unsplash.com/photo-1573088593824-52c03d56ec4f?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Topluluğun kurucu yönetmeni. Klasikleri çağdaş sahne diliyle buluşturan yorumlarıyla tanınıyor.", "plays": ["Bir Yaz Gecesi Rüyası", "Sefiller"]},
    {"id": str(uuid.uuid4()), "name": "Kerem Soylu", "role": "Yönetmen & Yapımcı", "type": "crew", "photo": "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Yapımcılığını üstlendiği oyunlar ulusal festivallerde sahnelendi. Sahne ekonomisi ve ritim ustası.", "plays": ["Hamlet", "Cimri"]},
    {"id": str(uuid.uuid4()), "name": "Aslı Güner", "role": "Yönetmen & Dramaturg", "type": "crew", "photo": "https://images.unsplash.com/photo-1544005313-94ddf0286df2?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Edebiyat uyarlamalarındaki incelikli dramaturjisiyle tanınır. Aynı zamanda sahnede oyuncu olarak yer alıyor.", "plays": ["Kürk Mantolu Madonna", "Martı"]},
    {"id": str(uuid.uuid4()), "name": "Emre Çetin", "role": "Işık Tasarımcısı", "type": "crew", "photo": "https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Atmosferik ışık tasarımlarıyla oyunlara görsel kimlik kazandırıyor. 15 yıllık sahne deneyimi.", "plays": ["Hamlet", "Sefiller", "Martı"]},
    {"id": str(uuid.uuid4()), "name": "Pınar Oral", "role": "Ses Tasarımcısı", "type": "crew", "photo": "https://images.unsplash.com/photo-1494790108377-be9c29b29330?crop=entropy&cs=srgb&fm=jpg&q=85&w=400", "bio": "Besteci ve ses tasarımcısı. Sefiller müzikalinin orkestrasyonunu üstlendi.", "plays": ["Sefiller", "Bir Yaz Gecesi Rüyası"]},
]

SEED_NEWS = [
    {"id": str(uuid.uuid4()), "title": "2026-2027 Sezonu Açıklandı: 4 Yeni Prömiyer", "category": "Duyuru", "image": "https://images.pexels.com/photos/713149/pexels-photo-713149.jpeg?auto=compress&cs=tinysrgb&w=800", "excerpt": "Yeni sezonda Sefiller müzikali ve Kürk Mantolu Madonna dahil dört yeni yapım seyirciyle buluşuyor.", "content": "Topluluğumuz 2026-2027 sezonunda repertuvarına dört yeni yapım ekliyor. Sezonun en iddialı projesi, canlı orkestra eşliğinde sahnelenecek Sefiller müzikali olacak. Sabahattin Ali'nin Kürk Mantolu Madonna'sı ise iki kişilik yoğun bir oda oyunu olarak Stüdyo Sahne'de prömiyer yapacak. Sezon boyunca toplam 120 gösterim planlanıyor; kombine bilet satışları önümüzdeki ay başlayacak.", "date": "2026-05-28"},
    {"id": str(uuid.uuid4()), "title": "Uluslararası Tiyatro Festivali'ne Davet Edildik", "category": "Festival", "image": "https://images.unsplash.com/photo-1514306191717-452ec28c7814?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "Hamlet yorumumuz bu yıl uluslararası festivalin ana seçkisinde sahnelenecek.", "content": "Kerem Soylu'nun yönettiği Hamlet, bu yıl düzenlenecek Uluslararası Tiyatro Festivali'nin ana seçkisine davet edildi. Festival komitesi, yapımın minimalist sahne tasarımını ve Baran Tekin'in performansını özellikle vurguladı. Festival gösterimleri eylül ayında gerçekleşecek.", "date": "2026-05-15"},
    {"id": str(uuid.uuid4()), "title": "Perde Arkası: Sefiller'in Dev Dekoru Nasıl Kuruldu?", "category": "Perde Arkası", "image": "https://images.unsplash.com/photo-1615414047026-802692414b79?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "Açık Hava Sahnesi'ndeki barikat dekorunun kurulumu tam üç hafta sürdü.", "content": "Sefiller müzikalinin ikonik barikat sahnesi için tasarlanan 8 metrelik dekor, atölyemizde altı kişilik bir ekip tarafından üç haftada üretildi. Dekorun her parçası, açık hava koşullarına dayanıklı malzemelerden hazırlandı ve sahne arkasında 12 dakikada kurulup sökülebiliyor. Işık tasarımcımız Emre Çetin, barikat sahnesinde 40'tan fazla ışık kaynağı kullanıyor.", "date": "2026-05-02"},
    {"id": str(uuid.uuid4()), "title": "Röportaj: Elif Yıldırım ile Sahnede 12 Yıl", "category": "Röportaj", "image": "https://images.unsplash.com/photo-1507924538820-ede94a04019d?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "\"Sahne benim için bir kaçış değil, hayatın ta kendisi\" diyen Yıldırım'la kariyerini konuştuk.", "content": "On iki yıldır topluluğumuzun sahnesinde olan Elif Yıldırım, bu sezon hem Bir Yaz Gecesi Rüyası'nda hem de Martı'da başrolde. \"Her oyun yeni bir hayat. Titania ile Nina aynı hafta içinde bambaşka iki kadın olmamı istiyor ve ben bu dönüşüme bayılıyorum\" diyor. Yıldırım, genç oyunculara tavsiyesini de paylaştı: \"Sahnede dürüst olun. Seyirci yalanı üç saniyede anlar.\"", "date": "2026-04-20"},
    {"id": str(uuid.uuid4()), "title": "Kombine Bilet Satışları Başladı: 6 Oyun Tek Pakette", "category": "Duyuru", "image": "https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "Sezonun tüm prömiyerlerini kapsayan kombine biletler %30 indirimle satışta.", "content": "2026-2027 sezonunun altı yapımını kapsayan kombine biletler satışa çıktı. Kombine sahipleri tüm prömiyerlere öncelikli giriş hakkı kazanıyor ve tek tek bilet fiyatına göre %30 tasarruf ediyor. Ayrıca kombine sahiplerine sezon boyunca sahne arkası turlarına ücretsiz katılım imkânı sunuluyor. Kontenjan 500 kombineyle sınırlı.", "date": "2026-06-05"},
    {"id": str(uuid.uuid4()), "title": "Çocuk Tiyatrosu Atölyesi Yaz Dönemi Kayıtları Açıldı", "category": "Duyuru", "image": "https://images.unsplash.com/photo-1499364615650-ec38552f4f34?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "7-12 yaş grubuna yönelik atölyede doğaçlama, ses ve beden çalışmaları yapılacak.", "content": "Her yaz düzenlediğimiz Çocuk Tiyatrosu Atölyesi'nin bu yılki kayıtları açıldı. Temmuz ve ağustos aylarında iki dönem hâlinde gerçekleşecek atölyede çocuklar doğaçlama, ses, beden ve sahne disiplini çalışmaları yapacak. Dönem sonunda katılımcılar Stüdyo Sahne'de aileleri için kısa bir gösterim sahneleyecek. Atölyeyi oyuncularımız Zeynep Arslan ve Can Demirci yürütüyor.", "date": "2026-05-22"},
    {"id": str(uuid.uuid4()), "title": "Röportaj: Kerem Soylu — \"Absürt Tiyatro Bugünü Anlatıyor\"", "category": "Röportaj", "image": "https://images.unsplash.com/photo-1547153760-18fc86324498?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "Godot'yu Beklerken'in yönetmeni Soylu, Beckett sahnelemenin zorluklarını anlattı.", "content": "Godot'yu Beklerken'i sahneye taşıyan Kerem Soylu, oyunun bugün hâlâ güncel olduğunu söylüyor: \"Beckett 1953'te yazdı ama bekleyiş hâli tam olarak bugünün hâli. Telefonumuza bakıp duruyoruz; Godot'dan haber var mı diye.\" Soylu, iki kişilik oyunda ritmin her şey olduğunu ekliyor: \"Mert ve Baran'la üç ay sadece tempo çalıştık. Sessizlikler repliklerden daha çok prova edildi.\"", "date": "2026-04-08"},
    {"id": str(uuid.uuid4()), "title": "Basında Biz: \"Şehrin En Cesur Sahnesi\"", "category": "Festival", "image": "https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "excerpt": "Ulusal basında çıkan değerlendirme yazısında topluluğumuz sezonun en cesur repertuvarıyla anıldı.", "content": "Geçtiğimiz hafta yayımlanan sezon değerlendirme yazısında PerdeSahne, \"şehrin en cesur sahnesi\" olarak nitelendi. Yazıda özellikle Bernarda Alba'nın Evi'nin tamamı kadın kadrosu ve Kürk Mantolu Madonna'nın iki kişilik yoğun anlatımı övgüyle karşılandı. Eleştirmen, açık hava sahnesinde yıldızlar altında izlenen Sefiller'i ise \"bu sezonun unutulmaz tiyatro anı\" olarak tanımladı.", "date": "2026-03-30"},
]

SEED_GALLERY = [
    {"id": str(uuid.uuid4()), "image": "https://images.pexels.com/photos/713149/pexels-photo-713149.jpeg?auto=compress&cs=tinysrgb&w=800", "caption": "Ana Sahne — Bir Yaz Gecesi Rüyası", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1507924538820-ede94a04019d?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Hamlet — Açılış sahnesi", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1514306191717-452ec28c7814?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Sefiller — Final perdesi", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1615414047026-802692414b79?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Ana Sahne salonumuz", "type": "Sahne Arkası"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1503095396549-807759245b35?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Kukla atölyesi çalışması", "type": "Sahne Arkası"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1629474468919-64a55bbae8eb?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Sezon afişleri", "type": "Sahne Arkası"},
    {"id": str(uuid.uuid4()), "image": "https://images.pexels.com/photos/12838778/pexels-photo-12838778.jpeg?auto=compress&cs=tinysrgb&w=800", "caption": "Cimri — Prova günleri", "type": "Sahne Arkası"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1587397845856-e6cf49176c70?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Kostüm provası", "type": "Sahne Arkası"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1499364615650-ec38552f4f34?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Romeo ve Juliet — Balkon sahnesi", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1516450360452-9312f5e86fc7?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Keşanlı Ali Destanı — Açılış gecesi", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1547153760-18fc86324498?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Godot'yu Beklerken — Işık provası", "type": "Oyun Kareleri"},
    {"id": str(uuid.uuid4()), "image": "https://images.unsplash.com/photo-1470229722913-7c0e2dbbafd3?crop=entropy&cs=srgb&fm=jpg&q=85&w=800", "caption": "Açık Hava Sahnesi — Yaz akşamı", "type": "Sahne Arkası"},
]


@app.on_event("startup")
async def seed_database():
    if await db.plays.count_documents({}) == 0:
        await db.plays.insert_many([dict(p) for p in SEED_PLAYS])
        logger.info("Seeded plays")
    if await db.team.count_documents({}) == 0:
        await db.team.insert_many([dict(t) for t in SEED_TEAM])
        logger.info("Seeded team")
    if await db.news.count_documents({}) == 0:
        await db.news.insert_many([dict(n) for n in SEED_NEWS])
        logger.info("Seeded news")
    if await db.gallery.count_documents({}) == 0:
        await db.gallery.insert_many([dict(g) for g in SEED_GALLERY])
        logger.info("Seeded gallery")
    # Admin seeding (idempotent, .env password change aware)
    admin = await db.users.find_one({"email": ADMIN_EMAIL})
    if admin is None:
        await db.users.insert_one({
            "id": str(uuid.uuid4()), "email": ADMIN_EMAIL,
            "password_hash": hash_password(ADMIN_PASSWORD),
            "name": "Admin", "role": "admin", "created_at": now_iso(),
        })
        logger.info("Seeded admin user")
    elif not verify_password(ADMIN_PASSWORD, admin["password_hash"]):
        await db.users.update_one({"email": ADMIN_EMAIL}, {"$set": {"password_hash": hash_password(ADMIN_PASSWORD)}})
    await db.users.create_index("email", unique=True)
    await db.login_attempts.create_index("identifier")


# ---------------- Content Endpoints ----------------
@api_router.get("/")
async def root():
    return {"message": "Perde Sahne Sanatları API"}


@api_router.get("/plays")
async def get_plays(genre: Optional[str] = None, venue: Optional[str] = None, age_limit: Optional[str] = None, search: Optional[str] = None):
    query = {}
    if genre:
        query["genre"] = genre
    if venue:
        query["venue"] = venue
    if age_limit:
        query["age_limit"] = age_limit
    if search:
        query["title"] = {"$regex": search, "$options": "i"}
    plays = await db.plays.find(query, {"_id": 0}).to_list(100)
    return plays


@api_router.get("/plays/{play_id}")
async def get_play(play_id: str):
    play = await db.plays.find_one({"id": play_id}, {"_id": 0})
    if not play:
        raise HTTPException(404, "Oyun bulunamadı")
    return play


@api_router.get("/showtimes")
async def get_showtimes():
    plays = await db.plays.find({}, {"_id": 0}).to_list(100)
    events = []
    for p in plays:
        for s in p.get("showtimes", []):
            events.append({
                "id": s["id"],
                "play_id": p["id"],
                "title": p["title"],
                "genre": p["genre"],
                "poster": p["poster"],
                "date": s["date"],
                "time": s["time"],
                "venue": s["venue"],
            })
    events.sort(key=lambda e: (e["date"], e["time"]))
    return events


@api_router.get("/team")
async def get_team():
    return await db.team.find({}, {"_id": 0}).to_list(100)


@api_router.get("/news")
async def get_news(category: Optional[str] = None):
    query = {"category": category} if category else {}
    news = await db.news.find(query, {"_id": 0}).to_list(100)
    news.sort(key=lambda n: n["date"], reverse=True)
    return news


@api_router.get("/news/{news_id}")
async def get_news_item(news_id: str):
    item = await db.news.find_one({"id": news_id}, {"_id": 0})
    if not item:
        raise HTTPException(404, "Haber bulunamadı")
    return item


@api_router.get("/gallery")
async def get_gallery():
    return await db.gallery.find({}, {"_id": 0}).to_list(100)


@api_router.get("/stats")
async def get_stats():
    plays_count = await db.plays.count_documents({})
    artists = await db.team.count_documents({"type": "cast"})
    tickets = await db.tickets.count_documents({})
    plays = await db.plays.find({}, {"_id": 0, "showtimes": 1}).to_list(100)
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    upcoming = sum(1 for p in plays for s in p.get("showtimes", []) if s["date"] >= today)
    return {
        "total_plays": plays_count,
        "total_audience": 12450 + tickets,
        "active_artists": artists,
        "upcoming_events": upcoming,
    }


@api_router.post("/contact")
async def create_contact(input: ContactCreate):
    doc = {"id": str(uuid.uuid4()), **input.model_dump(), "created_at": now_iso()}
    await db.contact_messages.insert_one(doc)
    return {"success": True, "message": "Mesajınız alındı. En kısa sürede dönüş yapacağız."}


@api_router.post("/newsletter")
async def subscribe_newsletter(input: NewsletterCreate):
    existing = await db.newsletter.find_one({"email": input.email})
    if existing:
        return {"success": True, "message": "Bu e-posta zaten kayıtlı."}
    await db.newsletter.insert_one({"id": str(uuid.uuid4()), "email": input.email, "created_at": now_iso()})
    return {"success": True, "message": "Bültene başarıyla kaydoldunuz!"}


@api_router.post("/discount/validate")
async def validate_discount(input: DiscountValidate):
    code = input.code.strip().upper()
    if code in DISCOUNT_CODES:
        return {"valid": True, "code": code, "percent": DISCOUNT_CODES[code]}
    return {"valid": False, "code": code, "percent": 0}


# ---------------- Auth Endpoints ----------------
@api_router.post("/auth/login")
async def admin_login(input: LoginRequest, request: Request, response: Response):
    email = input.email.strip().lower()
    identifier = f"{request.client.host}:{email}"
    attempt = await db.login_attempts.find_one({"identifier": identifier})
    if attempt and attempt.get("locked_until") and attempt["locked_until"] > now_iso():
        raise HTTPException(429, "Çok fazla başarısız deneme. 15 dakika sonra tekrar deneyin.")
    user = await db.users.find_one({"email": email})
    if not user or not verify_password(input.password, user["password_hash"]):
        count = (attempt.get("count", 0) + 1) if attempt else 1
        update = {"identifier": identifier, "count": count, "updated_at": now_iso()}
        if count >= 5:
            update["locked_until"] = (datetime.now(timezone.utc) + timedelta(minutes=15)).isoformat()
            update["count"] = 0
        await db.login_attempts.update_one({"identifier": identifier}, {"$set": update}, upsert=True)
        raise HTTPException(401, "E-posta veya şifre hatalı")
    await db.login_attempts.delete_one({"identifier": identifier})
    token = jwt.encode(
        {"sub": user["id"], "email": email, "type": "access",
         "exp": datetime.now(timezone.utc) + timedelta(hours=12)},
        JWT_SECRET, algorithm=JWT_ALGORITHM,
    )
    response.set_cookie("access_token", token, httponly=True, samesite="lax", max_age=43200, path="/")
    return {"token": token, "email": email, "name": user.get("name", "Admin"), "role": user["role"]}


@api_router.get("/auth/me")
async def auth_me(admin: dict = Depends(get_current_admin)):
    return admin


@api_router.post("/auth/logout")
async def auth_logout(response: Response):
    response.delete_cookie("access_token", path="/")
    return {"success": True}


# ---------------- Admin Endpoints ----------------
@api_router.get("/admin/summary")
async def admin_summary(admin: dict = Depends(get_current_admin)):
    tickets = await db.tickets.find({}, {"_id": 0, "qr_code": 0}).to_list(1000)
    return {
        "revenue": round(sum(t["amount"] for t in tickets), 2),
        "orders": len(tickets),
        "seats_sold": sum(t["quantity"] for t in tickets),
        "plays": await db.plays.count_documents({}),
        "messages": await db.contact_messages.count_documents({}),
        "subscribers": await db.newsletter.count_documents({}),
    }


@api_router.get("/admin/tickets")
async def admin_tickets(admin: dict = Depends(get_current_admin)):
    tickets = await db.tickets.find({}, {"_id": 0, "qr_code": 0}).to_list(1000)
    tickets.sort(key=lambda t: t["created_at"], reverse=True)
    return tickets


@api_router.get("/admin/messages")
async def admin_messages(admin: dict = Depends(get_current_admin)):
    msgs = await db.contact_messages.find({}, {"_id": 0}).to_list(1000)
    msgs.sort(key=lambda m: m["created_at"], reverse=True)
    return msgs


@api_router.get("/admin/subscribers")
async def admin_subscribers(admin: dict = Depends(get_current_admin)):
    subs = await db.newsletter.find({}, {"_id": 0}).to_list(1000)
    subs.sort(key=lambda s: s["created_at"], reverse=True)
    return subs


def _prepare_play_doc(input: PlayInput) -> dict:
    doc = input.model_dump()
    for s in doc["showtimes"]:
        if not s.get("id"):
            s["id"] = str(uuid.uuid4())
    return doc


@api_router.post("/admin/plays")
async def admin_create_play(input: PlayInput, admin: dict = Depends(get_current_admin)):
    doc = _prepare_play_doc(input)
    doc["id"] = str(uuid.uuid4())
    doc["views"] = 0
    await db.plays.insert_one({**doc})
    return doc


@api_router.put("/admin/plays/{play_id}")
async def admin_update_play(play_id: str, input: PlayInput, admin: dict = Depends(get_current_admin)):
    existing = await db.plays.find_one({"id": play_id}, {"_id": 0})
    if not existing:
        raise HTTPException(404, "Oyun bulunamadı")
    doc = _prepare_play_doc(input)
    doc["views"] = existing.get("views", 0)
    await db.plays.update_one({"id": play_id}, {"$set": doc})
    return {**doc, "id": play_id}


@api_router.delete("/admin/plays/{play_id}")
async def admin_delete_play(play_id: str, admin: dict = Depends(get_current_admin)):
    result = await db.plays.delete_one({"id": play_id})
    if result.deleted_count == 0:
        raise HTTPException(404, "Oyun bulunamadı")
    return {"success": True}


# ---------------- Payment / Ticket ----------------
def get_stripe(host_url: str) -> StripeCheckout:
    webhook_url = f"{host_url.rstrip('/')}/api/webhook/stripe"
    return StripeCheckout(api_key=STRIPE_API_KEY, webhook_url=webhook_url)


def ticket_email_html(t: dict) -> str:
    rows = [
        ("Oyun", t["play_title"]),
        ("Tarih", t["date"]),
        ("Saat", t["time"]),
        ("Salon", t["venue"]),
        ("Kategori", t["category"]),
        ("Adet", str(t["quantity"])),
        ("Tutar", f"₺{t['amount']:.2f}"),
        ("Bilet Kodu", t["code"]),
    ]
    rows_html = "".join(
        f'<tr><td style="padding:8px 16px;color:#888;font-size:13px;border-bottom:1px solid #eee;">{k}</td>'
        f'<td style="padding:8px 16px;font-size:14px;font-weight:600;border-bottom:1px solid #eee;">{v}</td></tr>'
        for k, v in rows
    )
    return f"""
    <table width="100%" cellpadding="0" cellspacing="0" style="font-family:Arial,sans-serif;max-width:560px;margin:0 auto;">
      <tr><td style="background:#09090B;padding:24px;text-align:center;">
        <span style="color:#FAFAFA;font-size:22px;">Em <span style="color:#D4AF37;">Tiyatro</span></span>
      </td></tr>
      <tr><td style="background:#D4AF37;padding:14px 24px;color:#000;font-weight:bold;font-size:16px;">DİJİTAL BİLETİNİZ HAZIR 🎭</td></tr>
      <tr><td style="background:#fff;padding:8px 0;">
        <p style="padding:0 16px;font-size:14px;">Sayın {t['customer_name']}, ödemeniz alındı. Bilet bilgileriniz aşağıdadır. Girişte ekteki QR kodu okutmanız yeterlidir.</p>
        <table width="100%" cellpadding="0" cellspacing="0">{rows_html}</table>
        <p style="padding:16px;font-size:12px;color:#888;">İyi seyirler dileriz! — Em Tiyatro, İstiklal Cad. No:142, Beyoğlu / İstanbul</p>
      </td></tr>
    </table>"""


async def send_ticket_email(ticket: dict):
    if not RESEND_API_KEY:
        logger.info("RESEND_API_KEY tanımlı değil, bilet e-postası atlandı")
        return
    params = {
        "from": SENDER_EMAIL,
        "to": [ticket["customer_email"]],
        "subject": f"Biletiniz Hazır — {ticket['play_title']} | Em Tiyatro",
        "html": ticket_email_html(ticket),
        "attachments": [{"filename": f"{ticket['code']}-qr.png", "content": ticket["qr_code"]}],
    }
    email = await asyncio.to_thread(resend.Emails.send, params)
    logger.info(f"Ticket email sent: {email.get('id')}")


async def issue_ticket(session_id: str):
    """Idempotent ticket creation after successful payment."""
    txn = await db.payment_transactions.find_one_and_update(
        {"session_id": session_id, "ticket_issued": {"$ne": True}, "payment_status": {"$ne": "paid"}},
        {"$set": {"ticket_issued": True, "payment_status": "paid", "updated_at": now_iso()}},
    )
    if not txn:
        return
    play = await db.plays.find_one({"id": txn["play_id"]}, {"_id": 0})
    st = next((s for s in play.get("showtimes", []) if s["id"] == txn["showtime_id"]), None) if play else None
    code = f"TKT-{uuid.uuid4().hex[:8].upper()}"
    ticket = {
        "id": str(uuid.uuid4()),
        "code": code,
        "session_id": session_id,
        "play_id": txn["play_id"],
        "play_title": play["title"] if play else "",
        "date": st["date"] if st else "",
        "time": st["time"] if st else "",
        "venue": st["venue"] if st else (play["venue"] if play else ""),
        "category": CATEGORY_LABELS.get(txn["category"], txn["category"]),
        "quantity": txn["quantity"],
        "customer_name": txn["customer_name"],
        "customer_email": txn["customer_email"],
        "amount": txn["amount"],
        "currency": txn["currency"],
        "qr_code": make_qr(f"{code} | {play['title'] if play else ''} | {st['date'] if st else ''} {st['time'] if st else ''}"),
        "created_at": now_iso(),
    }
    await db.tickets.insert_one({**ticket})
    logger.info(f"Ticket issued: {code}")
    try:
        await send_ticket_email(ticket)
    except Exception as e:
        logger.error(f"Bilet e-postası gönderilemedi: {e}")


@api_router.post("/checkout/session")
async def create_checkout(input: CheckoutCreate, request: Request):
    play = await db.plays.find_one({"id": input.play_id}, {"_id": 0})
    if not play:
        raise HTTPException(404, "Oyun bulunamadı")
    if input.category not in play["prices"]:
        raise HTTPException(400, "Geçersiz bilet kategorisi")
    showtime = next((s for s in play.get("showtimes", []) if s["id"] == input.showtime_id), None)
    if not showtime:
        raise HTTPException(404, "Gösterim bulunamadı")

    # Server-side price calculation only
    unit_price = float(play["prices"][input.category])
    discount_percent = 0
    if input.discount_code:
        discount_percent = DISCOUNT_CODES.get(input.discount_code.strip().upper(), 0)
    amount = round(unit_price * input.quantity * (1 - discount_percent / 100), 2)

    origin = input.origin_url.rstrip('/')
    success_url = f"{origin}/bilet/basarili?session_id={{CHECKOUT_SESSION_ID}}"
    cancel_url = f"{origin}/oyun/{input.play_id}"

    stripe_checkout = get_stripe(str(request.base_url))
    checkout_request = CheckoutSessionRequest(
        amount=amount,
        currency="try",
        success_url=success_url,
        cancel_url=cancel_url,
        metadata={
            "play_id": input.play_id,
            "showtime_id": input.showtime_id,
            "category": input.category,
            "quantity": str(input.quantity),
            "customer_email": input.customer_email,
        },
    )
    session: CheckoutSessionResponse = await stripe_checkout.create_checkout_session(checkout_request)

    await db.payment_transactions.insert_one({
        "id": str(uuid.uuid4()),
        "session_id": session.session_id,
        "play_id": input.play_id,
        "showtime_id": input.showtime_id,
        "category": input.category,
        "quantity": input.quantity,
        "discount_code": input.discount_code,
        "discount_percent": discount_percent,
        "customer_name": input.customer_name,
        "customer_email": input.customer_email,
        "amount": amount,
        "currency": "try",
        "payment_status": "pending",
        "status": "initiated",
        "ticket_issued": False,
        "created_at": now_iso(),
    })

    return {"url": session.url, "session_id": session.session_id}


@api_router.get("/checkout/status/{session_id}")
async def checkout_status(session_id: str, request: Request):
    txn = await db.payment_transactions.find_one({"session_id": session_id}, {"_id": 0})
    if not txn:
        raise HTTPException(404, "İşlem bulunamadı")

    stripe_checkout = get_stripe(str(request.base_url))
    cs: CheckoutStatusResponse = await stripe_checkout.get_checkout_status(session_id)

    await db.payment_transactions.update_one(
        {"session_id": session_id},
        {"$set": {"status": cs.status, "updated_at": now_iso()}},
    )
    if cs.payment_status == "paid":
        await issue_ticket(session_id)
    elif cs.status == "expired":
        await db.payment_transactions.update_one(
            {"session_id": session_id, "payment_status": {"$ne": "paid"}},
            {"$set": {"payment_status": "expired"}},
        )

    ticket = await db.tickets.find_one({"session_id": session_id}, {"_id": 0})
    return {
        "status": cs.status,
        "payment_status": cs.payment_status,
        "amount_total": cs.amount_total,
        "currency": cs.currency,
        "ticket": ticket,
    }


@api_router.post("/webhook/stripe")
async def stripe_webhook(request: Request):
    body = await request.body()
    signature = request.headers.get("Stripe-Signature")
    stripe_checkout = get_stripe(str(request.base_url))
    try:
        webhook_response = await stripe_checkout.handle_webhook(body, signature)
        if webhook_response.payment_status == "paid" and webhook_response.session_id:
            await issue_ticket(webhook_response.session_id)
    except Exception as e:
        logger.error(f"Webhook error: {e}")
        raise HTTPException(400, "Webhook hatası")
    return {"received": True}


app.include_router(api_router)

app.add_middleware(
    CORSMiddleware,
    allow_credentials=True,
    allow_origins=os.environ.get('CORS_ORIGINS', '*').split(','),
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.on_event("shutdown")
async def shutdown_db_client():
    client.close()
