import json
import os
import io
import sys


def forward(
    input_json,
    output_conf,
):
    with io.open(
        input_json,
        'r'
    ) as f:
        forward_nginx = json.load(f)

    with io.open(
        output_conf,
        'w'
    ) as f:
        names = [o['app_name'] for o in forward_nginx]

        if not '' in names:
            forward_nginx.append(
                dict(
                    app_name='',
                    redirect_url='https://product-development-service.blogspot.com',
                )
            )

        sections = dict()

        for entry in forward_nginx:
            location_path = None

            if entry['app_name'] != '':
                location_path = '/%s/' % entry['app_name']
            else:
                location_path = '/'

            if 'server_name' in entry:
                server_name = entry['server_name']
            else:
                server_name = 'default_server'

            if (
                not server_name in sections
            ):
                sections[server_name] = []

                if 'client_max_body_size' in entry:
                    client_max_body_size = entry['client_max_body_size']
                else:
                    client_max_body_size = '50M'

                assert isinstance(client_max_body_size, str)

                sections[server_name].append(
                    r'''
  client_max_body_size %s;
                    ''' % client_max_body_size
                )

            location_get = lambda location_body, location_path2, prefix=None,: (
                r'''
  location {location} {
    {location_body}
  }
                '''.replace(
                    '{location_body}', location_body,
                ).replace(
                    '{location}', '%s  %s' % (
                        (
                            '^~'
                            if prefix is None
                            else prefix
                        ),
                        location_path2
                    ),
                )
            )

            if 'target_endpoint' in entry:
                location_body_get = lambda target_endpoint: \
                    r'''
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $t1;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_redirect off;
    proxy_buffering off;
    proxy_pass {target_endpoint};
                    '''.replace(
                        '{target_endpoint}', target_endpoint,
                    )

                if 'fallback_endpoint' in entry:
                    fallback_name = '@fallback-%s' % os.urandom(10).hex()

                    sections[server_name].append(
                        location_get(
                            location_body_get(entry['target_endpoint']) + r'''
    proxy_intercept_errors on;
    error_page 502 =200 {fallback_name};
                            '''.replace(
                                '{fallback_name}', fallback_name,
                            ),
                            location_path,
                        )
                    )

                    sections[server_name].append(
                        location_get(
                            location_body_get(entry['fallback_endpoint']),
                            fallback_name,
                            '',
                        )
                    )
                else:
                    sections[server_name].append(
                        location_get(
                            location_body_get(entry['target_endpoint']),
                            location_path,
                        )
                    )
            elif 'redirect_url' in entry:
                sections[server_name].append(
                    location_get(
                        r'''
      return 302 {redirect_url}$request_uri;
                        '''.replace(
                            '{redirect_url}', entry['redirect_url'],
                        ),
                        location_path,
                    )
                )
            else:
                raise NotImplementedError

        servers = []

        for server_name, current_sections in sections.items():
            servers.append(
                r'''
server {
  set $t1 $remote_addr;
  if ($http_x_forwarded_for)
  {
    set $t1 $http_x_forwarded_for;
  }

  server_name {server_name};
  listen 80 {default_server};
  #client_max_body_size 50M;

  {sections_config}
}
                '''.replace(
                  '{sections_config}', '\n'.join(current_sections)
                ).replace(
                  '{server_name}',
                  (
                    '_'
                    if server_name == 'default_server'
                    else server_name
                  ),
                ).replace(
                  '{default_server}',
                  (
                    ''
                    if not server_name == 'default_server'
                    else server_name
                  )
                )
            )

        f.write(r'''
events {
  multi_accept on;
  worker_connections 64;
}

http {
  log_format main
    '[$time_local][$remote_addr:$remote_port, $http_x_forwarded_for, $t1, $http_host]'
    '[$request_length,$bytes_sent,$request_time]'
    '[$status][$request]'
    '[$http_user_agent][$http_referer]';

  access_log /dev/null combined;
  access_log /dev/stderr main;
  gzip on;
  server_tokens off;

  {servers_config}

  map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
  }
}
        '''.replace(
          '{servers_config}', '\n'.join(servers)
        ))

def ssl(input_json, output_conf):
    with io.open(
        input_json,
        'r'
    ) as f:
        ssl_nginx = json.load(f)

    servers = []

    if 'stream_server' in ssl_nginx:
        upstream_servers = []
        server_names = []

        if 'by_server_name' in ssl_nginx['stream_server']:
            for k, v in ssl_nginx['stream_server']['by_server_name'].items():
                upstream_servers.append(
                    'upstream %s { server %s; }' % (
                        v['upstream_name'],
                        v['url'],
                    )
                )
                server_names.append(
                    '"%s" %s;' % (
                        v['server_name'], v['upstream_name'],
                    )
                )
            

        if 'ssh' in ssl_nginx['stream_server']:
            ssh_section = 'upstream ssh { server {ssh}; }'.replace(
                '{ssh}',
                ssl_nginx['stream_server']['ssh'],
            )
        else:
            ssh_section = ''

        ssl_port = 444
        stream_server = r'''
stream {
    upstream web {
        server 127.0.0.1:444;
    }

{upstream_servers}

    {ssh_section}

    map $ssl_preread_protocol $upstream_protocol {
        default ssh;
        "TLSv1.2" $upstream_server_name;
        "TLSv1.3" $upstream_server_name;
    }

    map $ssl_preread_server_name $upstream_server_name {
        default web;
{server_names}
    }

    # SSH and SSL on the same port
    server {
        listen 443;

        ssl_preread on;
        proxy_pass $upstream_protocol;
    }
}
        '''.replace(
            '{upstream_servers}', ''.join([
                '    ' + o + '\n'
                for o in upstream_servers
            ]),
        ).replace(
            '{ssh_section}', ssh_section,
        ).replace(
            '{server_names}', ''.join([
                '        ' + o + '\n'
                for o in server_names
            ]),
        )
    else:
        stream_server = ''
        ssl_port = 443

    if 'default_server' in ssl_nginx:
        server = ssl_nginx['default_server']

        servers.append(
            r'''
server {
  set $t1 $remote_addr;
  if ($http_x_forwarded_for)
  {
    set $t1 $http_x_forwarded_for;
  }

  listen {ssl_port} ssl default_server;
  server_name _;

  client_max_body_size {client_max_body_size};

  ssl_certificate {signed_chain_cert};
  ssl_certificate_key {domain_key};

  return 444;
}
            '''.replace(
                  '{signed_chain_cert}', server['signed_chain_cert'],
              ).replace(
                  '{client_max_body_size}', server['client_max_body_size'],
              ).replace(
                  '{domain_key}', server['domain_key'],
              ).replace(
                  '{ssl_port}', '%d' % ssl_port,
              )
        )

    for server in ssl_nginx['servers']:
        servers.append(
            r'''


server {
  set $t1 $remote_addr;
  if ($http_x_forwarded_for)
  {
    set $t1 $http_x_forwarded_for;
  }

  listen 80;
  server_name {server_names};
  client_max_body_size {client_max_body_size};

  location ~ ^/.well-known/acme-challenge/ {
    alias /var/www/;
    try_files $uri =404;
  }

  location ~ {
    #return 444;
    return 301 https://$host$request_uri;
  }
}

server {
  set $t1 $remote_addr;
  if ($http_x_forwarded_for)
  {
    set $t1 $http_x_forwarded_for;
  }

  listen {ssl_port} ssl;
  server_name {server_names};

  client_max_body_size {client_max_body_size};

  ssl_certificate {signed_chain_cert};
  ssl_certificate_key {domain_key};

  location ^~ / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $connection_upgrade;
    proxy_redirect off;
    proxy_buffering off;
    proxy_pass http://app:80;
  }
}
            '''.replace(
                '{server_names}', ' '.join(server['server_names'])
              ).replace(
                  '{signed_chain_cert}', server['signed_chain_cert'],
              ).replace(
                  '{client_max_body_size}', server['client_max_body_size'],
              ).replace(
                  '{domain_key}', server['domain_key'],
              ).replace(
                  '{ssl_port}', '%d' % ssl_port,
              )
        )


    with io.open(
        output_conf,
        'w'
    ) as f:
        f.write(
            r'''
load_module "modules/ngx_stream_module.so";

events {
  multi_accept on;
  worker_connections 64;
}

{stream_server}

http {
  log_format main
  '[$time_local][$remote_addr:$remote_port, $http_x_forwarded_for, $t1, $http_host]'
  '[$request_length,$bytes_sent,$request_time]'
  '[$status][$request]'
  '[$http_user_agent][$http_referer]';

  access_log /dev/null combined;
  access_log /dev/stderr main;
  gzip on;
  server_tokens off;

  {servers}


  map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
  }
}
            '''\
                .replace('{servers}', '\n'.join(servers)) \
                .replace('{stream_server}', stream_server)
        )


if __name__ == '__main__':
    if len(sys.argv) >= 2 and sys.argv[1] == 'ssl':
        ssl(*sys.argv[2:])
    else:
        forward(*sys.argv[1:])
