#!/usr/bin/env python3

import argparse
import os
import re
import shutil
import sys
import urllib.parse
from datetime import datetime
from email.utils import parsedate_to_datetime
import ftplib

# 外部ライブラリが必要です: pip install requests beautifulsoup4
try:
    import requests
    from bs4 import BeautifulSoup
except ImportError:
    print("Error: 'requests' and 'beautifulsoup4' libraries are required.", file=sys.stderr)
    print("Please install them using: pip install requests beautifulsoup4", file=sys.stderr)
    sys.exit(1)


# デフォルトのソースパス。環境に合わせて変更してください。
# 例:
# COE_tkProg_source = r"\\192.168.27.2\share\apps\tkProg\tklib"  # ローカル/UNC
# COE_tkProg_source = "https://example.com/path/to/files/"      # HTTP
# COE_tkProg_source = "ftp://user:pass@ftp.example.com/path/" # FTP
COE_tkProg_source = r"\\192.168.27.2\share\apps\tkProg\tklib"
COE_tkProg_dest   = "tklib"

# 拒否するパスの正規表現文字列のリスト
reject_patterns = [
    r"__pycache__$",
    r"\.pyc$",
    r"\.prev$",
    r"\.junk$",
    r" - コピー",
    r"kamiya",
    r"personal",
]

def parse_args():
    """コマンドライン引数を解析する"""
    parser = argparse.ArgumentParser(
        description="Recursively copy or sync files from source to destination, skipping rejected patterns. Supports local, http(s), and ftp(s) sources."
    )
    parser.add_argument(
        "-s", "--source",
        type=str, default=COE_tkProg_source,
        help="Source directory or URL (http/ftp) to copy files from"
    )
    parser.add_argument(
        "-d", "--dest",
        type=str, default=COE_tkProg_dest,
        help="Destination directory to copy files to"
    )
    parser.add_argument(
        "-m", "--maxlevel",
        type=int, default=-1,
        help="Maximum recursion depth. 0 for no recursion, -1 for infinite (default), N for N levels."
    )
    return parser.parse_args()

def handle_user_choice(prompt):
    """ユーザーに選択肢を提示し、有効な入力を待つ"""
    print(prompt)
    print("Options: [Y]es, [N]o, [A]ll, [R]etain All, [S]top")
    while True:
        choice = input("Your choice (y/n/a/r/s): ").lower()
        if choice in ['y', 'n', 'a', 'r', 's']:
            return choice
        print("Invalid input, please enter 'y', 'n', 'a', 'r', or 's'.")

def should_reject(path, compiled_patterns):
    """パスが拒否パターンに一致するかをチェックする"""
    return any(pattern.search(path) for pattern in compiled_patterns)

# --- Remote Walker Functions ---

def walk_http(base_url, compiled_patterns, maxlevel=-1, current_level=0):
    """HTTP(S)サーバー上のディレクトリを再帰的に探索するジェネレータ"""
    if maxlevel != -1 and current_level > maxlevel:
        return

    try:
        response = requests.get(base_url)
        response.raise_for_status()
    except requests.RequestException as e:
        print(f"Error accessing URL {base_url}: {e}", file=sys.stderr)
        return

    soup = BeautifulSoup(response.text, 'html.parser')
    dirs, files = [], []

    for link in soup.find_all('a'):
        href = link.get('href')
        if not href or href.startswith('?') or href.startswith('/') or ':' in href:
            continue # 親ディレクトリや絶対パス、クエリなどは無視

        full_url = urllib.parse.urljoin(base_url, href)
        
        # Last-Modifiedヘッダーから更新日時を取得 (サーバーが対応している場合)
        mtime = 0
        try:
            head_resp = requests.head(full_url)
            if 'Last-Modified' in head_resp.headers:
                mtime = parsedate_to_datetime(head_resp.headers['Last-Modified']).timestamp()
        except requests.RequestException:
            pass # HEADリクエスト失敗は無視

        if href.endswith('/'): # ディレクトリと見なす
            if not should_reject(href, compiled_patterns):
                dirs.append(href)
        else: # ファイルと見なす
            if not should_reject(href, compiled_patterns):
                files.append({'name': href, 'mtime': mtime})

    yield base_url, [d.strip('/') for d in dirs], files

    if maxlevel == -1 or current_level < maxlevel:
        for d in dirs:
            next_url = urllib.parse.urljoin(base_url, d)
            yield from walk_http(next_url, compiled_patterns, maxlevel, current_level + 1)

def walk_ftp(ftp_conn, path, compiled_patterns, maxlevel=-1, current_level=0):
    """FTPサーバー上のディレクトリを再帰的に探索するジェネレータ"""
    if maxlevel != -1 and current_level > maxlevel:
        return

    try:
        original_cwd = ftp_conn.pwd()
        ftp_conn.cwd(path)
    except ftplib.error_perm as e:
        print(f"Error accessing FTP path {path}: {e}", file=sys.stderr)
        return

    items = ftp_conn.nlst()
    dirs, files = [], []

    for item_name in items:
        if item_name in ('.', '..'):
            continue
        
        # MDTMコマンドで更新日時を取得
        mtime = 0
        try:
            # MDTMコマンドのレスポンス '213 YYYYMMDDHHMMSS' をパース
            mdtm_str = ftp_conn.voidcmd(f'MDTM {item_name}')[4:].strip()
            mtime = datetime.strptime(mdtm_str, '%Y%m%d%H%M%S').timestamp()
        except ftplib.error_perm:
             # MDTMをサポートしていないサーバーもある
            pass

        # ディレクトリかファイルかを判別
        is_dir = False
        try:
            ftp_conn.cwd(item_name)
            is_dir = True
            ftp_conn.cwd('..')
        except ftplib.error_perm:
            is_dir = False

        if is_dir:
            if not should_reject(item_name, compiled_patterns):
                dirs.append(item_name)
        else:
            if not should_reject(item_name, compiled_patterns):
                files.append({'name': item_name, 'mtime': mtime})

    yield path, dirs, files
    
    # 元のディレクトリに戻す
    ftp_conn.cwd(original_cwd)

    if maxlevel == -1 or current_level < maxlevel:
        for d in dirs:
            next_path = f"{path.rstrip('/')}/{d}"
            yield from walk_ftp(ftp_conn, next_path, compiled_patterns, maxlevel, current_level + 1)


# --- File Sync Logic ---

def sync_files(source, dest, compiled_patterns, maxlevel, protocol):
    """ファイルを再帰的にコピー/同期する"""
    global_user_choice = None
    
    print(f"\n[Phase 1] Syncing files from {source} to {dest}...")

    # ウォーカーを選択
    walker = None
    ftp_conn = None # FTP接続を維持するため
    
    if protocol == 'local':
        source_root_depth = source.rstrip(os.sep).count(os.sep)
        # os.walkをジェネレータでラップして形式を統一
        def local_walker():
            for root, dirs, files in os.walk(source):
                current_depth = root.rstrip(os.sep).count(os.sep) - source_root_depth
                if maxlevel != -1 and current_depth > maxlevel:
                    del dirs[:]
                    continue
                
                # dirs[:] の変更は os.walk の挙動に影響する
                dirs[:] = [d for d in dirs if not should_reject(d, compiled_patterns)]
                
                # ファイル情報を辞書のリストに変換
                file_info_list = []
                for f in files:
                    if not should_reject(f, compiled_patterns):
                        src_path = os.path.join(root, f)
                        file_info_list.append({'name': f, 'mtime': os.path.getmtime(src_path)})

                yield root, dirs, file_info_list
        walker = local_walker()

    elif protocol.startswith('http'):
        walker = walk_http(source, compiled_patterns, maxlevel)
        
    elif protocol.startswith('ftp'):
        try:
            parsed_url = urllib.parse.urlparse(source)
            use_tls = protocol == 'ftps'
            FTP_CLASS = ftplib.FTP_TLS if use_tls else ftplib.FTP
            ftp_conn = FTP_CLASS()
            ftp_conn.connect(parsed_url.hostname, parsed_url.port or (990 if use_tls else 21))
            ftp_conn.login(parsed_url.username, parsed_url.password)
            if use_tls:
                ftp_conn.prot_p() # データ接続を暗号化
            
            ftp_path = parsed_url.path or '/'
            walker = walk_ftp(ftp_conn, ftp_path, compiled_patterns, maxlevel)
        except Exception as e:
            print(f"FTP connection failed: {e}", file=sys.stderr)
            sys.exit(1)

    if not walker:
        print("Could not determine a valid walker for the source.", file=sys.stderr)
        sys.exit(1)

    # メインループ
    for root, dirs, files in walker:
        # 相対パスを計算
        if protocol == 'local':
            rel_dir = os.path.relpath(root, source)
        else: # http, ftp
            rel_dir = os.path.relpath(urllib.parse.urlparse(root).path, urllib.parse.urlparse(source).path)
        
        dest_dir = os.path.join(dest, rel_dir) if rel_dir != "." else dest
        os.makedirs(dest_dir, exist_ok=True)
        
        # (この部分は元のロジックと同じ) 宛先にある拒否ファイルを処理
        # ... (この部分は変更がないため省略)

        for file_info in files:
            file_name = file_info['name']
            src_mtime = file_info['mtime']
            dest_path = os.path.join(dest_dir, file_name)

            if not os.path.exists(dest_path) or (src_mtime and src_mtime > os.path.getmtime(dest_path)):
                print(f"Copying '{file_name}' to '{dest_dir}'...")
                try:
                    if protocol == 'local':
                        src_path = os.path.join(root, file_name)
                        shutil.copy2(src_path, dest_path)
                    
                    elif protocol.startswith('http'):
                        src_url = urllib.parse.urljoin(root, file_name)
                        with requests.get(src_url, stream=True) as r:
                            r.raise_for_status()
                            with open(dest_path, 'wb') as f:
                                shutil.copyfileobj(r.raw, f)
                        if src_mtime: os.utime(dest_path, (src_mtime, src_mtime))
                                
                    elif protocol.startswith('ftp'):
                        # FTPのパスは絶対パスで扱う
                        remote_path = f"{root.rstrip('/')}/{file_name}"
                        with open(dest_path, 'wb') as f:
                            ftp_conn.retrbinary(f'RETR {remote_path}', f.write)
                        if src_mtime: os.utime(dest_path, (src_mtime, src_mtime))
                        
                    print(f"  -> Copied '{file_name}'.")

                except Exception as e:
                    print(f"  -> FAILED to copy '{file_name}': {e}", file=sys.stderr)
    
    if ftp_conn:
        ftp_conn.quit()


def main():
    """メイン関数"""
    args = parse_args()
    source = args.source
    dest = os.path.abspath(args.dest)

    # プロトコルを判定
    protocol = 'local'
    if source.lower().startswith('https://'):
        protocol = 'https'
    elif source.lower().startswith('http://'):
        protocol = 'http'
    elif source.lower().startswith('ftps://'):
        protocol = 'ftps'
    elif source.lower().startswith('ftp://'):
        protocol = 'ftp'
    
    if protocol == 'local':
        source = os.path.abspath(args.source)
        if not os.path.isdir(source):
            print(f"Error: Source directory '{source}' not found or is not a directory.", file=sys.stderr)
            sys.exit(1)
    else:
        # URLの末尾が'/'で終わるように正規化
        if not source.endswith('/'):
            source += '/'

    print(f"\nStarting file sync from '{source}' to '{dest}' with maxlevel={args.maxlevel}")
    
    compiled_patterns = [re.compile(p, re.IGNORECASE) for p in reject_patterns]

    sync_files(source, dest, compiled_patterns, args.maxlevel, protocol)

    print("\nSync process finished.")


if __name__ == "__main__":
    main()