#!/usr/bin/env python3
"""Synthetic redirect demo. Run: python3 redirect-query-demo.py

Binds an ephemeral port on 127.0.0.1, requests only its own HTTP server,
bypasses system proxies and writes no files. No tags or real click IDs.
This is a transport exercise, not a conversion-attribution test.
"""
import json
import sys
from http.server import BaseHTTPRequestHandler, HTTPServer
from socketserver import TCPServer
from threading import Thread
from urllib.parse import parse_qsl, parse_qs, urlencode, urlsplit
from urllib.request import HTTPRedirectHandler, ProxyHandler, build_opener

QUERY = 'gclid=AdP_Test_Only_123&utm_source=google&utm_medium=cpc'


class LoopbackServer(HTTPServer):
    def server_bind(self):
        # HTTPServer normally reverse-resolves its address. This fixed numeric
        # loopback exercise needs no hostname lookup or external DNS dependency.
        TCPServer.server_bind(self)
        self.server_name = '127.0.0.1'
        self.server_port = self.server_address[1]


class DemoHandler(BaseHTTPRequestHandler):
    def log_message(self, *args):
        pass

    def do_GET(self):
        request = urlsplit(self.path)
        if request.path == '/landing':
            body = b'Synthetic landing page. No measurement tags.\n'
            self.send_response(200)
            self.send_header('Content-Type', 'text/plain; charset=utf-8')
            self.send_header('Content-Length', str(len(body)))
            self.end_headers()
            self.wfile.write(body)
            return
        if request.path not in ('/strip', '/lower', '/keep'):
            self.send_error(404)
            return
        query = request.query
        if request.path == '/strip':
            query = ''
        elif request.path == '/lower':
            query = urlencode([(key, value.lower() if key == 'gclid' else value)
                               for key, value in parse_qsl(query, keep_blank_values=True)])
        # Fixed local destination: this server does not accept a redirect URL.
        self.send_response(302)
        self.send_header('Location', '/landing' + ('?' + query if query else ''))
        self.send_header('Content-Length', '0')
        self.end_headers()


class LocalTrace(HTTPRedirectHandler):
    def __init__(self, origin):
        self.origin = origin
        self.hops = []

    def redirect_request(self, req, fp, code, msg, headers, newurl):
        target = urlsplit(newurl)
        if (target.scheme + '://' + target.netloc != self.origin or
                target.path != '/landing' or len(self.hops) >= 3):
            raise ValueError('Demo redirect outside its fixed local destination')
        self.hops.append({'status': code, 'from_path': urlsplit(req.full_url).path,
                          'to_path': target.path})
        return super().redirect_request(req, fp, code, msg, headers, newurl)


def run_demo(query=QUERY):
    server = LoopbackServer(('127.0.0.1', 0), DemoHandler)
    thread = Thread(target=server.serve_forever, daemon=True)
    origin = 'http://127.0.0.1:' + str(server.server_port)
    output = []
    thread.start()
    try:
        before = parse_qs(query, keep_blank_values=True).get('gclid', [])
        for scenario, path in [('stripped_query', '/strip'),
                               ('lowercased_value', '/lower'),
                               ('preserved_query', '/keep')]:
            trace = LocalTrace(origin)
            # Disable proxy environment variables even for the loopback request.
            opener = build_opener(ProxyHandler({}), trace)
            with opener.open(origin + path + ('?' + query if query else ''), timeout=5) as response:
                response.read()
                final = urlsplit(response.geturl())
                after = parse_qs(final.query, keep_blank_values=True).get('gclid', [])
                state = 'missing' if not after else 'retained' if after == before else 'changed'
                output.append({'scenario': scenario, 'http_status': response.status,
                               'redirects': trace.hops, 'query_before': query,
                               'query_after': final.query, 'gclid_before': before,
                               'gclid_after': after, 'gclid_status': state})
    finally:
        server.shutdown()
        server.server_close()
        thread.join(timeout=5)
    return output


if __name__ == '__main__':
    if len(sys.argv) != 1:
        sys.exit('Usage: python3 redirect-query-demo.py (no URL arguments)')
    print(json.dumps(run_demo(), indent=2))
