#!/usr/bin/env python3
"""
Omarchy Disk Configuration Tool

Interactive partition editor and validator for Omarchy installations.
"""

import json
import sys
from pathlib import Path


def _find_root_partition(disk_config):
  for dev_mod in disk_config.device_modifications:
    for part in dev_mod.partitions:
      if part.mountpoint == Path('/'):
        return part
      
      if part.btrfs_subvols:
        for subvol in part.btrfs_subvols:
          if subvol.mountpoint == Path('/'):
            return part
  
  return None


def validate_disk_config(config, interactive=True):
  from archinstall.lib.models.device import FilesystemType, Size, Unit, EncryptionType
  from archinstall.lib.output import info, warn
  
  if not config.disk_config:
    return 'CONTINUE'
  
  validation_warnings = []
  
  boot_partition = None
  for dev_mod in config.disk_config.device_modifications:
    for part in dev_mod.partitions:
      if part.mountpoint == Path('/boot') or part.mountpoint == Path('/efi'):
        boot_partition = part
        break
  
  if boot_partition:
    min_boot_size = Size(2, Unit.GiB, boot_partition.length.sector_size)
    if boot_partition.length >= min_boot_size:
      size_gb = boot_partition.length.convert(Unit.GiB).value
      info(f'✓ Boot partition size: {size_gb:.1f} GiB')
    else:
      size_mb = boot_partition.length.convert(Unit.MiB).value
      warn(f'⚠ Boot partition is only {size_mb:.0f} MiB')
      warn('  Omarchy recommends at least 2 GiB for boot partition')
      warn('  Multiple kernels may not fit')
      validation_warnings.append('boot_size')
  else:
    warn('⚠ Could not find boot partition (/boot or /efi)')
    warn('  System may not boot correctly')
    validation_warnings.append('no_boot')
  
  root_partition = _find_root_partition(config.disk_config)
  
  if root_partition:
    if root_partition.fs_type == FilesystemType.Btrfs:
      info('✓ Root filesystem is btrfs')
      
      if root_partition.btrfs_subvols:
        subvol_names = [str(sv.name) for sv in root_partition.btrfs_subvols]
        subvol_mounts = {str(sv.mountpoint): str(sv.name) for sv in root_partition.btrfs_subvols}
        
        required_subvols = {
          '/': '@',
          '/home': '@home',
          '/var/log': '@log',
          '/var/cache/pacman/pkg': '@pkg',
        }
        
        missing_subvols = []
        for mount, expected_name in required_subvols.items():
          if mount not in subvol_mounts:
            missing_subvols.append(f'{expected_name} → {mount}')
          elif subvol_mounts[mount] != expected_name:
            warn(f'⚠ Subvolume at {mount} is named "{subvol_mounts[mount]}" not "{expected_name}"')
        
        if missing_subvols:
          warn(f'⚠ Missing recommended subvolumes: {", ".join(missing_subvols)}')
          warn('  Omarchy recommends: @, @home, @log, @pkg')
          warn('  Some features (like Snapper) may not work optimally')
          info(f'  Current subvolumes: {", ".join(subvol_names)}')
          validation_warnings.append('missing_subvols')
        else:
          info(f'✓ Btrfs subvolumes: {", ".join(subvol_names)}')
      else:
        warn('⚠ Btrfs partition has no subvolumes defined')
        warn('  Omarchy recommends subvolumes for snapshots')
        warn('  Required: @ (root), @home, @log, @pkg')
        validation_warnings.append('no_subvols')
    else:
      fs_name = root_partition.fs_type.value if root_partition.fs_type else 'unknown'
      warn(f'⚠ Root filesystem is {fs_name}, not btrfs')
      warn('  Omarchy is designed for btrfs with snapshots')
      warn('  Some features may not work correctly')
      validation_warnings.append('not_btrfs')
    
    is_encrypted = False
    if config.disk_config.disk_encryption:
      enc = config.disk_config.disk_encryption
      if enc.encryption_type != EncryptionType.NoEncryption:
        is_encrypted = root_partition in enc.partitions
    
    if is_encrypted:
      info('✓ Root partition is encrypted with LUKS')
      
      if config.disk_config.disk_encryption.iter_time != 2000:
        old_time = config.disk_config.disk_encryption.iter_time
        config.disk_config.disk_encryption.iter_time = 2000
        info(f'✓ Adjusted iteration time: {old_time}ms → 2000ms')
      else:
        info('✓ Iteration time: 2000ms (optimal)')
    else:
      warn('⚠ Root partition is NOT encrypted')
      warn('  Omarchy recommends LUKS encryption for security')
      validation_warnings.append('no_encryption')
  else:
    warn('⚠ Could not identify root partition')
    validation_warnings.append('no_root')
  
  if validation_warnings and interactive:
    import subprocess
    import shutil
    
    gum_path = shutil.which('gum')
    if gum_path:
      warn('')
      
      try:
        result = subprocess.run(
          ['gum', 'choose', '--header', 'Validation warnings detected. What would you like to do?',
           'Re-edit partitions', 'Continue anyway', 'Abort'],
          capture_output=True,
          text=True,
        )
        
        choice = result.stdout.strip() if result.stdout else ''
        
        if choice == 'Re-edit partitions':
          return 'RE_EDIT'
        elif choice == 'Abort':
          return 'ABORT'
        else:
          info('Continuing despite warnings')
          return 'CONTINUE'
      
      except (subprocess.CalledProcessError, KeyboardInterrupt):
        return 'ABORT'
  
  return 'CONTINUE'


def apply_omarchy_partition_defaults():
  import archinstall.lib.interactions.disk_conf as disk_conf_module
  from archinstall.lib.models.device import (
    PartitionModification, ModificationStatus, PartitionType, 
    Size, Unit, SectorSize, FilesystemType, PartitionFlag,
    DeviceModification, BDevice
  )
  from archinstall.lib.interactions.disk_conf import get_default_btrfs_subvols
  from archinstall.lib.disk.device_handler import device_handler
  
  def _boot_partition_2gib(sector_size: SectorSize, using_gpt: bool) -> PartitionModification:
    flags = [PartitionFlag.BOOT]
    size = Size(2, Unit.GiB, sector_size)
    start = Size(1, Unit.MiB, sector_size)
    if using_gpt:
      flags.append(PartitionFlag.ESP)
    
    return PartitionModification(
      status=ModificationStatus.Create,
      type=PartitionType.Primary,
      start=start,
      length=size,
      mountpoint=Path('/boot'),
      fs_type=FilesystemType.Fat32,
      flags=flags,
    )
  
  def _select_main_filesystem_btrfs() -> FilesystemType:
    return FilesystemType.Btrfs
  
  def _select_mount_options_compressed() -> list[str]:
    return ['compress=zstd']
  
  def _suggest_single_disk_auto_subvolumes(
    device: BDevice,
    filesystem_type: FilesystemType | None = None,
    separate_home: bool | None = None,
  ):
    if not filesystem_type:
      filesystem_type = FilesystemType.Btrfs
    
    if filesystem_type == FilesystemType.Btrfs:
      using_subvolumes = True
      mount_options = ['compress=zstd']
    else:
      using_subvolumes = False
      mount_options = []
    
    sector_size = device.device_info.sector_size
    device_modification = DeviceModification(device, wipe=True)
    using_gpt = device_handler.partition_table.is_gpt()
    
    boot_partition = _boot_partition_2gib(sector_size, using_gpt)
    device_modification.add_partition(boot_partition)
    
    total_size = device.device_info.total_size
    available_space = total_size - boot_partition.length - Size(1, Unit.MiB, sector_size)
    
    root_partition = PartitionModification(
      status=ModificationStatus.Create,
      type=PartitionType.Primary,
      start=boot_partition.start + boot_partition.length,
      length=available_space,
      mountpoint=None if using_subvolumes else Path('/'),
      fs_type=filesystem_type,
      mount_options=mount_options,
    )
    
    if using_subvolumes:
      root_partition.btrfs_subvols = get_default_btrfs_subvols()
    
    device_modification.add_partition(root_partition)
    return device_modification
  
  disk_conf_module._boot_partition = _boot_partition_2gib
  disk_conf_module.select_main_filesystem_format = _select_main_filesystem_btrfs
  disk_conf_module.select_mount_options = _select_mount_options_compressed
  disk_conf_module.suggest_single_disk_layout = _suggest_single_disk_auto_subvolumes


def load_config(config_file: Path, creds_file: Path | None = None):
  from archinstall.lib.args import ArchConfig, Arguments
  
  with open(config_file) as f:
    config_data = json.load(f)
  
  if creds_file and creds_file.exists():
    with open(creds_file) as f:
      creds_data = json.load(f)
      config_data.update(creds_data)
  
  args = Arguments(
    config=config_file,
    creds=creds_file,
    mountpoint=Path('/mnt'),
    silent=True,
  )
  
  return ArchConfig.from_config(config_data, args)


def save_config(config, output_file: Path):
  from archinstall.lib.output import info
  
  try:
    config_dict = config.safe_json()
    with open(output_file, 'w') as f:
      json.dump(config_dict, f, indent=2, default=str)
    info(f'✓ Configuration saved to: {output_file}')
  except Exception as e:
    from archinstall.lib.output import error
    error(f'Failed to save config: {e}')
    raise


def main():
  import argparse
  
  parser = argparse.ArgumentParser(description='Omarchy Disk Configuration Tool')
  parser.add_argument('--config', type=Path, required=True, help='Path to config file')
  parser.add_argument('--creds', type=Path, help='Path to credentials file')
  parser.add_argument('--output', type=Path, help='Output path (default: overwrites input)')
  parser.add_argument('--non-interactive', action='store_true', help='Skip interactive prompts')
  
  args = parser.parse_args()
  
  output_file = args.output or args.config
  
  if not args.config.exists():
    print(f'ERROR: Config file not found: {args.config}', file=sys.stderr)
    sys.exit(1)
  
  if args.creds and not args.creds.exists():
    print(f'ERROR: Credentials file not found: {args.creds}', file=sys.stderr)
    sys.exit(1)
  
  try:
    from archinstall.lib.output import info, error
    from archinstall.lib.disk.disk_menu import DiskLayoutConfigurationMenu
    from archinstall.tui.curses_menu import Tui
    
    apply_omarchy_partition_defaults()
    
    info('Loading configuration...')
    config = load_config(args.config, args.creds)
    
    if not config.disk_config:
      error('No disk configuration found in config file')
      sys.exit(1)
    
    while True:
      info('Launching partition editor...')
      
      with Tui():
        edited_disk_config = DiskLayoutConfigurationMenu(config.disk_config).run()
        if edited_disk_config:
          config.disk_config = edited_disk_config
          info('✓ Partition configuration updated')
        else:
          info('No changes made in partition editor')
      
      interactive = not args.non_interactive
      validation_result = validate_disk_config(config, interactive=interactive)
      
      if validation_result == 'RE_EDIT':
        continue
      elif validation_result == 'ABORT':
        info('Disk configuration cancelled by user')
        sys.exit(1)
      else:
        break
    
    save_config(config, output_file)
    info('✓ Disk configuration complete!')
    
  except KeyboardInterrupt:
    print('\nCancelled by user', file=sys.stderr)
    sys.exit(1)
  except Exception as e:
    print(f'ERROR: {e}', file=sys.stderr)
    import traceback
    traceback.print_exc()
    sys.exit(1)


if __name__ == '__main__':
  main()
