#!python
# https://github.com/tindy2013/subconverter
# https://v2rayse.com/en/v2ray-clash
# https://realpython.com/python-yaml
import argparse
from proxyUtil import *

ch = logging.StreamHandler()
ch.setFormatter(CustomFormatter())
logging.basicConfig(level=logging.INFO, handlers=[ch])

startSubConverter = f"docker run -d --rm --name 'subconverter' -p 25500:25500 tindy2013/subconverter:latest"
stopSubConverter = f"docker stop subconverter"

# https://github.com/blackmatrix7/ios_rule_script/tree/master/rule/Clash
# https://github.com/ACL4SSR/ACL4SSR/tree/master/Clash
# https://github.com/Hackl0us/SS-Rule-Snippet
# https://github.com/chiroots/iran-hosted-domains
# https://github.com/MasterKia/PersianBlocker
# https://github.com/farrokhi/adblock-iran

DIRECT_RULE_SET = [
    # name, behavior, url
    ("iran", "classical", "https://github.com/SamadiPour/iran-hosted-domains/releases/latest/download/clash_rules.yaml"),
    ("private", "domain", "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/private.txt"),
    #("lancidr", "ipcidr", "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/lancidr.txt"),
]

REJECT_RULE_SET = [
    # name, behavior, url
    ("adblock", "domain", "https://raw.githubusercontent.com/Loyalsoldier/clash-rules/release/reject.txt"),
]

def checkSubConverter():
    for i in range(10):
        try:
            res = requests.get('http://localhost:25500/version').text
        except:
            time.sleep(1)
            continue
        if "subconverter" in res :
            break
    else:
        sys.exit("subconverter start failed")


def mySubprocessRun(cmd):
    logging.debug(f"run {cmd}")
    p = subprocess.run(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # subprocess.DEVNULL
    if p.returncode:
        logging.error(f"run {cmd} failed")
        logging.error(p.stderr.decode())
    return p.returncode, p.stdout.decode(), p.stderr.decode()


def getRuleSet(behavior, url, policy="DIRECT"):
    try:
        res = requests.get(url)
    except:
        logging.error(f"get {url} failed")
        return []
    if res.status_code != 200:
        logging.error(f"get {url} failed")
        return []
    rules = yaml.safe_load(res.text)['payload']
    logging.info(f"got {len(rules)} rules from {url}")
    if behavior == "classical":
        return [*map( lambda s: f"{s},{policy}", rules)] 
    elif behavior == "domain":
        return [*map( lambda s: f"DOMAIN,{s},{policy}", rules)] 
    elif behavior == "ipcidr":
        return [*map( lambda s: f"IP-CIDR,{s},{policy}", rules)]
    else:
        logging.error(f"unknown behavior {behavior}")
        return []


def main():
    parser = argparse.ArgumentParser(description="Simple Clash Config Generator")
    parser.add_argument("-f", "--file", help="file contain ss proxy")
    parser.add_argument('--url', help="get proxy from url")
    parser.add_argument('--stdin', help="get proxy from stdin", action='store_true', default=False)
    parser.add_argument('--free', help="get free proxy", action='store_true', default=False)
    parser.add_argument('--dns', help="use DNS server", action='store_true', default=False)
    parser.add_argument('--rule', help="use rules", action='store_true', default=False)
    # https://github.com/Dreamacro/clash/wiki/Clash-Premium-Features
    parser.add_argument('--premium', help="use Clash Premium Features", action='store_true', default=False)
    parser.add_argument('-v', "--verbose", help="increase output verbosity", action="store_true", default=False)
    parser.add_argument('-vv', '--debug', help="debug log", action='store_true', default=False)
    parser.add_argument('-o', '--output', help="output file (default: clashConfig.yaml)", default='clashConfig.yaml')
    args = parser.parse_args()
    
    if args.verbose:
        logging.getLogger().setLevel(logging.INFO)
    if args.debug:
        logging.getLogger().setLevel(logging.DEBUG)

    lines = set()
    if args.file and os.path.isfile(args.file):
        with open(args.file, 'r', encoding='UTF-8') as file:
            lines.update( parseContent(file.read().strip()) )
            logging.info(f"got {len(lines)} from reading proxy from file")

    if args.url :
        lines.update( ScrapURL(args.url) )
    
    if args.free :
        lines.update( ScrapURL('https://raw.githubusercontent.com/freefq/free/master/v2') )

    if args.stdin :
        lines.update( parseContent(sys.stdin.read()) )

    lines = list(lines)
    logging.info(f"We have {len(lines)} proxy")
    
    if not lines:
        logging.error("No proxy to check")
        parser.print_help(sys.stderr)
        return
    
    installDocker()
    
    with open(CLASH_SAMPLE_PATH) as f:
        myclash = yaml.load(f, Loader=yaml.RoundTripLoader)

    if not args.dns:
        myclash.pop('dns')

    if args.premium:
        rulesets = {}
        for name, behavior, url in (DIRECT_RULE_SET+REJECT_RULE_SET):
            rulesets[f"{name}"] = {
                    "type": "http",
                    "behavior": f"{behavior}",
                    "url": f"{url}",
                    "path": f"./ruleset/{name}.yaml",
                    "interval": 86400
            }
        myclash['rule-providers'] = rulesets
        rules = [ f"RULE-SET,{ruleset[0]},DIRECT" for ruleset in DIRECT_RULE_SET]
        rules += [ f"RULE-SET,{ruleset[0]},REJECT" for ruleset in REJECT_RULE_SET]
        rules.append("MATCH,🔆 LIST")
        myclash['rules'] = rules

    elif args.rule:
        myclash.pop('rule-providers')
        rules = []
        for name, behavior, url in DIRECT_RULE_SET:
            rules.extend( getRuleSet(behavior, url, "DIRECT"))
        for name, behavior, url in REJECT_RULE_SET:
            rules.extend( getRuleSet(behavior, url, "REJECT"))
        rules.append("MATCH,🔆 LIST")
        myclash['rules'] = rules
    else:
        myclash.pop('rule-providers')
    
    
    returncode, stdout, stderr = mySubprocessRun(startSubConverter)
    if returncode != 0:
        returncode, stdout, stderr = mySubprocessRun(stopSubConverter)
        returncode, stdout, stderr = mySubprocessRun(startSubConverter)
        if returncode != 0:
            logging.error(f"start clash failed, {stdout}, {stderr}")
            return

    checkSubConverter()

    URLEncode = '|'.join( map( quote, lines) )
    res = requests.get(f"http://127.0.0.1:25500/sub?target=clash&url={URLEncode}", timeout=10)

    clashyml = yaml.safe_load(res.text)
    proxyNames = [proxy['name'] for proxy in clashyml['proxies']]

    myclash['proxies'] = clashyml['proxies']

    # "🔆 LIST"
    extended = ["🔥 Auto(Best ping)", "Auto-Fallback", "⚖️ load-balance hash", "⚖️ load-balance round-robin", "DIRECT", "REJECT"] 
    myclash['proxy-groups'][0]['proxies'] = extended + proxyNames

    # "🔥 Auto(Best ping)"
    myclash['proxy-groups'][1]['proxies'] = proxyNames

    # "Auto-Fallback"
    myclash['proxy-groups'][2]['proxies'] = proxyNames

    # "⚖️ load-balance hash"
    myclash['proxy-groups'][3]['proxies'] = proxyNames

    # "⚖️ load-balance round-robin"
    myclash['proxy-groups'][4]['proxies'] = proxyNames

    with open(args.output, 'w') as f:
        #yaml.dump(myclash, f, default_flow_style=False, sort_keys=False, indent=4)
        yaml.dump(myclash, f, Dumper=yaml.RoundTripDumper, allow_unicode = True, encoding = None, indent=4)
        logging.info(f"clash config saved to {args.output}")

    returncode, stdout, stderr = mySubprocessRun(stopSubConverter)
    logging.info("subconverter stopped")


if __name__ == '__main__':
    main()
