#!/usr/bin/python3

import socket
import common_ports
import os
import sys
import re
from concurrent.futures import ThreadPoolExecutor


def scan_port(ip_addr, port):
    try:
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.settimeout(1)
        result = s.connect_ex((ip_addr, port))
        s.close()
        if result == 0:
            return port
    except:
        return None
    return None


def get_open_ports(target, port_range, verbose=False):
    open_ports = []

    # Try resolving the target
    try:
        ip_addr = socket.gethostbyname(target)
    except socket.gaierror:
        if re.match(r'^\d{1,3}(\.\d{1,3}){3}$', target):
            return "Error: Invalid IP address"
        else:
            return "Error: Invalid hostname"

    # Build the list of ports from range
    ports_list = list(range(port_range[0], port_range[1] + 1))

    with ThreadPoolExecutor(max_workers=100) as executor:
        futures = [executor.submit(scan_port, ip_addr, port) for port in ports_list]
        for future in futures:
            result = future.result()
            if result:
                open_ports.append(result)

    # Output
    if verbose:
        try:
            hostname = socket.gethostbyaddr(ip_addr)[0]
        except socket.herror:
            hostname = target

        output = f"Open ports for {hostname} ({ip_addr})\nPORT     SERVICE\n"
        try:
            import common_ports
            for port in open_ports:
                service = common_ports.ports_and_services.get(port, 'unknown')
                output += f"{port:<9}{service}\n"
        except ImportError:
            for port in open_ports:
                output += f"{port:<9}unknown\n"
        return output.strip()

    return open_ports


# print(get_open_ports("scanme.nmap.org", [20, 80], verbose=True))

def main():
    if len(sys.argv) < 2:
        print("Usage: python3 portscan.py <ip_or_file> [start_port] [end_port] [--verbose]")
        return

    input_arg = sys.argv[1]
    start_port = int(sys.argv[2]) if len(sys.argv) > 2 else 20
    end_port = int(sys.argv[3]) if len(sys.argv) > 3 else 1024
    verbose = "--verbose" in sys.argv

    targets = []

    if os.path.isfile(input_arg):
        with open(input_arg) as f:
            targets = [line.strip() for line in f if line.strip()]
    else:
        targets = [input_arg]

    for target in targets:
        print(get_open_ports(target, [start_port, end_port], verbose))
        print("-" * 40)


if __name__ == "__main__":
    main()
