import logging
import os
import random
import re
import uuid
from locust import HttpUser, task, between, events

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)
logger = logging.getLogger("locust")

SEED_USERS = []
ALL_USERNAMES = []
USER_UIDS = []
POST_UIDS = []
POST_SLUGS = []
PROJECT_UIDS = []
COMMENT_UIDS = []
NEWS_UIDS = []
NEWS_SLUGS = []
TOPICS = ["devlog", "showcase", "question", "rant", "fun", "random"]
PROJECT_TYPES = ["game", "game_asset", "software", "mobile_app", "website"]
AVATAR_STYLES = [
    "adventurer", "adventurer-neutral", "avataaars", "bottts", "identicon",
    "initials", "lorelei", "micah", "open-peeps", "pixel-art", "shapes",
]
UUID_RE = r'[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}'
PASSWORD = "testpass123"


def random_username():
    return f"lu_{uuid.uuid4().hex[:10]}"


def do_signup(client, username, email):
    with client.post("/auth/signup", data={
        "username": username, "email": email,
        "password": PASSWORD, "confirm_password": PASSWORD,
    }, catch_response=True, name="signup") as resp:
        if resp.status_code == 200 and resp.history:
            resp.success()
            return True
        resp.failure(f"signup failed: status={resp.status_code}")
        return False


def do_login(client, email):
    with client.post("/auth/login", data={
        "email": email, "password": PASSWORD,
    }, catch_response=True, name="login") as resp:
        if resp.status_code == 200 and resp.history:
            resp.success()
            return True
        resp.failure(f"login failed: status={resp.status_code}")
        return False


@events.init.add_listener
def seed_data(environment, **kwargs):
    host = environment.host or "http://127.0.0.1:10502"
    logger.info(f"Seeding {host}...")

    import urllib.request
    import urllib.parse
    import http.cookiejar

    created = 0
    while created < 5:
        username = random_username()
        email = f"{username}@locust.devplace"
        body = urllib.parse.urlencode({
            "username": username, "email": email,
            "password": PASSWORD, "confirm_password": PASSWORD,
        }).encode()
        req = urllib.request.Request(f"{host}/auth/signup", data=body)
        try:
            urllib.request.urlopen(req, timeout=10)
            SEED_USERS.append({"username": username, "email": email})
            ALL_USERNAMES.append(username)
            created += 1
        except Exception as e:
            logger.warning(f"Create seed user failed: {e}")

    logger.info(f"Created {len(SEED_USERS)} seed users")

    def logged_in_opener(email):
        jar = http.cookiejar.CookieJar()
        opener = urllib.request.build_opener(
            urllib.request.HTTPCookieProcessor(jar)
        )
        body = urllib.parse.urlencode({"email": email, "password": PASSWORD}).encode()
        opener.open(
            urllib.request.Request(f"{host}/auth/login", data=body), timeout=10
        )
        return opener

    for seed in SEED_USERS[:3]:
        try:
            opener = logged_in_opener(seed["email"])
            body = urllib.parse.urlencode({
                "content": "x " * 100,
                "title": f"Seed post {uuid.uuid4().hex[:6]}",
                "topic": random.choice(TOPICS),
            }).encode()
            resp = opener.open(
                urllib.request.Request(f"{host}/posts/create", data=body),
                timeout=10,
            )
            m = re.search(r"/posts/(\S+)", resp.url)
            if m and m.group(1) not in POST_SLUGS:
                POST_SLUGS.append(m.group(1))
            # extract full UUID from the response HTML
            html = resp.read().decode("utf-8", errors="replace")
            m2 = re.search(rf'name="post_uid"\s+value="({UUID_RE})"', html)
            if m2 and m2.group(1) not in POST_UIDS:
                POST_UIDS.append(m2.group(1))
        except Exception as e:
            logger.warning(f"Create seed post failed: {e}")

    logger.info(f"Created {len(POST_UIDS)} seed posts")

    for seed in SEED_USERS[:2]:
        try:
            opener = logged_in_opener(seed["email"])
            body = urllib.parse.urlencode({
                "content": f"Seed comment by {seed['username']}",
                "post_uid": POST_UIDS[0],
            }).encode()
            opener.open(
                urllib.request.Request(f"{host}/comments/create", data=body),
                timeout=10,
            )
        except Exception as e:
            logger.warning(f"Create seed comment failed: {e}")

    for seed in SEED_USERS[:2]:
        try:
            opener = logged_in_opener(seed["email"])
            body = urllib.parse.urlencode({
                "title": f"Project {uuid.uuid4().hex[:6]}",
                "description": "z " * 50,
                "project_type": random.choice(PROJECT_TYPES),
            }).encode()
            opener.open(
                urllib.request.Request(f"{host}/projects/create", data=body),
                timeout=10,
            )
        except Exception as e:
            logger.warning(f"Create seed project failed: {e}")

    # ── Seed news articles (direct DB insert) ───────────────────
    try:
        from devplacepy.database import get_table, init_db
        from devplacepy.utils import make_combined_slug
        init_db()
        news_table = get_table("news")
        for i in range(3):
            title = f"Locust news article {i} {uuid.uuid4().hex[:4]}"
            article_uid = str(uuid.uuid4())
            slug = make_combined_slug(title, article_uid)
            news_table.insert({
                "uid": article_uid, "slug": slug, "title": title,
                "external_id": f"locust_seed_{i}", "grade": 8,
                "status": "published", "show_on_landing": 1,
                "source_name": "Locust", "synced_at": "2026-01-01T00:00:00",
                "description": "Locust seed article for load testing.",
            })
            NEWS_UIDS.append(article_uid)
            NEWS_SLUGS.append(slug)
    except Exception as e:
        logger.warning(f"Seed news articles failed: {e}")

    # ── Harvest UIDs from HTML responses ──────────────────────
    #   projects sidebar:  /projects?user_uid={uuid}
    #   project cards:     /votes/project/{uuid}
    #   comment deletion:  /comments/delete/{uuid}

    uuid_pat = rf'({UUID_RE})'

    for seed in SEED_USERS:
        try:
            opener = logged_in_opener(seed["email"])
            resp = opener.open(
                urllib.request.Request(f"{host}/projects"), timeout=10
            )
            html = resp.read().decode("utf-8", errors="replace")
            m = re.search(rf'/projects\?user_uid={uuid_pat}', html)
            if m and m.group(1) not in USER_UIDS:
                USER_UIDS.append(m.group(1))
            proj_matches = re.findall(rf'/votes/project/{uuid_pat}', html)
            PROJECT_UIDS.extend(
                p for p in proj_matches if p not in PROJECT_UIDS
            )
        except Exception as e:
            logger.warning(f"Harvest from {seed['username']} failed: {e}")

    if POST_UIDS:
        try:
            opener = logged_in_opener(SEED_USERS[0]["email"])
            resp = opener.open(
                urllib.request.Request(f"{host}/posts/{POST_UIDS[0]}"),
                timeout=10,
            )
            html = resp.read().decode("utf-8", errors="replace")
            matches = re.findall(rf'/comments/delete/{uuid_pat}', html)
            COMMENT_UIDS.extend(m for m in matches if m not in COMMENT_UIDS)
        except Exception as e:
            logger.warning(f"Harvest comment UIDs failed: {e}")

    logger.info(
        f"Seed complete: {len(SEED_USERS)} users, {len(POST_UIDS)} posts, "
        f"{len(PROJECT_UIDS)} projects, {len(COMMENT_UIDS)} comments, "
        f"{len(USER_UIDS)} uids"
    )


class DevPlaceUser(HttpUser):
    wait_time = between(1, 5)

    def on_start(self):
        if random.random() < 0.3 and SEED_USERS:
            seed = random.choice(SEED_USERS)
            do_login(self.client, seed["email"])
            self.username = seed["username"]
        else:
            username = random_username()
            email = f"{username}@locust.devplace"
            do_signup(self.client, username, email)
            self.username = username
            ALL_USERNAMES.append(username)

    # ── browsing ────────────────────────────────────────────────

    @task(5)
    def view_feed(self):
        tab = random.choice(["all", "trending", "recent", "following"])
        topic = random.choice([None] + TOPICS)
        params = {"tab": tab}
        if topic:
            params["topic"] = topic
        self.client.get("/feed", params=params, name="feed")

    @task(4)
    def view_post(self):
        slugs = POST_SLUGS if POST_SLUGS else POST_UIDS
        if not slugs:
            return
        self.client.get(
            f"/posts/{random.choice(slugs)}", name="posts/[uid]"
        )

    @task(3)
    def view_profile(self):
        if not ALL_USERNAMES:
            return
        self.client.get(
            f"/profile/{random.choice(ALL_USERNAMES)}", name="profile/[username]"
        )

    @task(3)
    def view_projects(self):
        self.client.get("/projects", params={
            "tab": random.choice(["recent", "released", "popular", "new"]),
        }, name="projects")

    @task(1)
    def view_landing(self):
        self.client.get("/", name="landing")

    @task(1)
    def view_auth_forms(self):
        self.client.get(
            random.choice(["/auth/signup", "/auth/login"]), name="auth/form"
        )

    @task(3)
    def view_news(self):
        self.client.get("/news", name="news")

    @task(2)
    def view_news_detail(self):
        slugs = NEWS_SLUGS if NEWS_SLUGS else NEWS_UIDS
        if not slugs:
            return
        self.client.get(
            f"/news/{random.choice(slugs)}", name="news/[slug]"
        )

    @task(1)
    def view_bugs(self):
        self.client.get("/bugs", name="bugs")

    # ── auth flows ──────────────────────────────────────────────

    @task(1)
    def login_flow(self):
        if not SEED_USERS:
            return
        do_login(self.client, random.choice(SEED_USERS)["email"])

    @task(1)
    def logout_and_relogin(self):
        self.client.get("/auth/logout", name="auth/logout")
        self.client.get("/", name="landing")
        if SEED_USERS:
            do_login(self.client, random.choice(SEED_USERS)["email"])

    # ── content creation ────────────────────────────────────────

    @task(2)
    def create_post(self):
        data = {
            "content": "x " * 100,
            "title": f"Load test {uuid.uuid4().hex[:6]}",
            "topic": random.choice(TOPICS),
        }
        with self.client.post("/posts/create", data=data,
                              catch_response=True,
                              name="posts/create") as resp:
            if resp.status_code == 200 and resp.history:
                m = re.search(r"/posts/(\S+)", resp.url)
                if m and m.group(1) not in POST_SLUGS:
                    POST_SLUGS.append(m.group(1))
                m2 = re.search(rf'name="post_uid"\s+value="({UUID_RE})"', resp.text)
                if m2 and m2.group(1) not in POST_UIDS:
                    POST_UIDS.append(m2.group(1))
                resp.success()
            else:
                resp.failure("post not created")

    @task(2)
    def comment_on_post(self):
        if not POST_UIDS:
            return
        self.client.post("/comments/create", data={
            "content": f"Comment {uuid.uuid4().hex[:6]} " * 3,
            "post_uid": random.choice(POST_UIDS),
        }, name="comments/create")

    @task(1)
    def create_project(self):
        self.client.post("/projects/create", data={
            "title": f"Project {uuid.uuid4().hex[:6]}",
            "description": "y " * 50,
            "project_type": random.choice(PROJECT_TYPES),
        }, name="projects/create")

    # ── social interaction ──────────────────────────────────────

    @task(2)
    def vote_on_post(self):
        if not POST_UIDS:
            return
        self.client.post(
            f"/votes/post/{random.choice(POST_UIDS)}",
            data={"value": random.choice([1, -1])},
            name="votes/post",
        )

    @task(1)
    def vote_on_project(self):
        if not PROJECT_UIDS:
            return
        self.client.post(
            f"/votes/project/{random.choice(PROJECT_UIDS)}",
            data={"value": random.choice([1, -1])},
            name="votes/project",
        )

    @task(1)
    def follow_user(self):
        targets = [u for u in ALL_USERNAMES if u != self.username]
        if not targets:
            return
        self.client.post(
            f"/follow/{random.choice(targets)}", name="follow/[username]"
        )

    @task(1)
    def unfollow_user(self):
        targets = [u for u in ALL_USERNAMES if u != self.username]
        if not targets:
            return
        self.client.post(
            f"/follow/unfollow/{random.choice(targets)}", name="follow/unfollow"
        )

    @task(1)
    def send_message(self):
        if not USER_UIDS or not SEED_USERS:
            return
        target_uid = random.choice(USER_UIDS)
        self.client.post("/messages/send", data={
            "content": f"Msg from {self.username}",
            "receiver_uid": target_uid,
        }, name="messages/send")

    # ── user self-management ────────────────────────────────────

    @task(1)
    def update_profile(self):
        self.client.post("/profile/update", data={
            "bio": f"Updated at {uuid.uuid4().hex[:6]}",
            "location": random.choice(["NL", "US", "DE", "UK", " "]),
        }, name="profile/update")

    @task(1)
    def view_messages(self):
        self.client.get("/messages", name="messages")

    @task(1)
    def view_notifications(self):
        self.client.get("/notifications", name="notifications")

    @task(1)
    def mark_all_notifications_read(self):
        self.client.post("/notifications/mark-all-read", name="notifications/mark-all-read")

    @task(1)
    def delete_comment(self):
        if not COMMENT_UIDS:
            return
        self.client.post(
            f"/comments/delete/{random.choice(COMMENT_UIDS)}",
            name="comments/delete",
        )

    # ── static / media ──────────────────────────────────────────

    @task(2)
    def view_multiavatar(self):
        seed = random.choice(ALL_USERNAMES) if ALL_USERNAMES else "anon"
        self.client.get(
            f"/avatar/multiavatar/{seed}?size=32", name="avatar/multiavatar"
        )

    @task(1)
    def comment_on_news(self):
        if not NEWS_UIDS:
            return
        self.client.post("/comments/create", data={
            "content": f"News comment {uuid.uuid4().hex[:6]} " * 2,
            "target_uid": random.choice(NEWS_UIDS),
            "target_type": "news",
        }, name="comments/news")
