use strict;
use warnings;
use Carp;

package CommitBit::Model::Repository;
use Jifty::DBI::Schema;
use File::Spec;
use File::Path;
use Cwd 'abs_path';

use CommitBit::Record schema {

        column name =>
            type is 'text',
            is distinct;
            is mandatory;
        column local_path =>
            type is 'text';
        column description =>
            type is 'text',
            render_as 'textarea';
        column member_url =>
            type is 'text';
        column anon_url => 
            type is 'text';


};

# Your model-specific methods go here.

sub projects {
    my $self = shift;
    my $projects = CommitBit::Model::ProjectCollection->new();
    $projects->limit(column => 'repository', value => $self->id);
    return ($projects);

}

sub associated_users {
    my $self    = shift;
    my $users   = CommitBit::Model::UserCollection->new();
    my $members = $users->join(
        alias1  => 'main',
        column1 => 'id',
        table2  => 'project_members',
        column2 => 'person'
    );
    my $projects = $users->join(
        alias1  => $members,
        column1 => 'project',
        table2  => 'projects',
        column2 => 'id'
    );
    my $repositories = $users->join(
        alias1  => $projects,
        column1 => 'repository',
        table2  => 'repositories',
        column2 => 'id'
    );
    $users->limit(
        alias  => $repositories,
        column => 'id',
        value  => $self->id
    );
    return $users;
}

# XXX: make sure svn/svnadmin is in path during compile time.
sub _svnadmin {
    my $self = shift;
    $self->run_cmd("svnadmin", @_);
}

sub _svn {
    my $self = shift;
    $self->run_cmd("svn", @_);
}

sub with_write_lock (&) {
    my $sub = shift;
    eval {
    obtain_write_lock() || Carp::croak "Couldn't get write lock :(";
    &$sub();
    release_write_lock() || Carp::croak "Couldn't release write lock :("; 
    return 1;
    };
    warn $@ if $@;
    return 0;
}


sub create {
    my $self = shift;
    my $args = { @_ };
    my $prefix = abs_path( Jifty->config->app("repository_prefix") );


    if (exists $args->{local_path} and length $args->{local_path}) {
             if ( ! File::Spec->file_name_is_absolute( $args->{'local_path'} )) {
	        $args->{local_path} = File::Spec->catdir($prefix, $args->{local_path});
             }

    } else {
        	$args->{local_path} = File::Spec->catdir($prefix, $args->{name});
    }

    my ($val) = $self->SUPER::create(%$args);
    if ($self->id) {
	    # XXX: when failed rollback the create?
        warn "my id is ".$self->id;
        $self->init_repository;
    }
    return ($val);
}

sub init_repository {
    my $self = shift;

    if ( -d $self->local_path ) {

    } else {

        $self->mkdir_parent( $self->local_path );
        $self->_svnadmin( 'create', $self->local_path );
        $self->write_svnserv_config;
        $self->write_httpd_config;
    }
}

sub mkdir_parent {
    my $self = shift;
    my $path = shift;
    my @parts = File::Spec->splitdir($path);
    pop @parts;
    File::Path::mkpath([File::Spec->catdir(@parts)]);


}

sub add_project {
    my $self    = shift;
    my $project = shift;

    my @project_structure = ( '', 'trunk', 'branches', 'tags' );

    my @paths = map {
        "file://"
            . File::Spec::Unix->catdir( $self->local_path,
            $project->root_path, $_ )
    } @project_structure;

    foreach my $path (@paths) {
        $self->_svn( 'ls', $path );
        if ($?) {    # If the path isn't there
            $self->_svn(
                'mkdir',
                -m => 'Project '
                    . $project->name
                    . ' init by CommitBit '
                    . $CommitBit::VERSION,
                $path
            );

        }

    }

    $self->write_authz_file();
}

sub remove_project {}

sub write_password_files {
    my $self = shift;
    $self->write_htpasswd_file();
    $self->write_svnserve_passwd_file();
}

sub write_htpasswd_file {
    my $self = shift;
    with_write_lock {
        open my $fh, '>', $self->svnconf_file_path('htpasswd') or die $@;
        print $fh $self->autogenerated_file_warning;
        my $users = $self->associated_users;
        while (my $user = $users->next) {
            my $line = `htpasswd -nmb @{[$user->email]} @{[$user->__value('password')]}`;
            chomp $line;
            print $fh $line;
        }
    };
}

sub write_svnserve_passwd_file {
    my $self = shift;
    with_write_lock {
        open my $fh, '>', $self->svnconf_file_path('passwd') or die $@;
        print $fh $self->autogenerated_file_warning;
        print $fh "[users]\n";
        my $users = $self->associated_users;
        while (my $user = $users->next) {
            print $fh $user->email. ' = '. $user->__value('password'). "\n";
        }

	# all users that involves in any project in the repository
    };
}

sub write_authz_file {
    my $self = shift;

    my $authz = {};
    my $projects = CommitBit::Model::ProjectCollection->new();
    $projects->limit(column => 'repository', value => $self->id);

    with_write_lock {

        open( my $file, ">", $self->svnconf_file_path('authz') ) || die $@;
        print $file $self->autogenerated_file_warning;
        while ( my $project = $projects->next ) {
            print $file "[/" . $project->root_path . "]\n" || die $@;
            foreach my $user ( @{
                                    $project->members->items_array_ref,
                                    $project->administrators->items_array_ref
                                } ) {
                print $file $user->email . " = rw\n" || die $@;
            }
            if ($project->publicly_visible) {
                print $file "* = r\n";
            }
        }

        close($file) || die $@;

    };

}

sub svnconf_file_path {
    my $self = shift;
    return File::Spec->catdir($self->local_path, 'conf', @_);
}


sub write_svnserv_config {
    my $self = shift;
    with_write_lock {
        open my $file, ">", $self->svnconf_file_path('svnserve.conf') or die $@;
	print $file $self->autogenerated_file_warning;
	print $file <<"EOF";
[general]
anon-access = read
auth-access = write
password-db = passwd
authz-db = authz
realm = @{[$self->name]}
EOF

	close $file or die $@;
    };



}
sub write_httpd_config {
    my $self = shift;
    my $msg = $self->autogenerated_file_warning;
 $msg .= <<"EOF";

<Location /@{[$self->name]}>

  # Uncomment this to enable the repository,
  DAV svn

  # Set this to the path to your repository
  SVNPath @{[$self->local_path]}

  # The following allows for basic http authentication.  Basic authentication
  # should not be considered secure for any particularly rigorous definition of
  # secure.

  # Uncomment the following 3 lines to enable Basic Authentication
  AuthType Basic
  AuthName "@{[$self->name]} Subversion Repository"
  AuthUserFile @{[$self->svnconf_file_path('htpasswd')]}

  # Uncomment the following line to enable Authz Authentication
  AuthzSVNAccessFile @{[$self->svnconf_file_path('authz')]}

  # The following three lines allow anonymous read, but make
  # committers authenticate themselves.

  <LimitExcept GET PROPFIND OPTIONS REPORT>
    Require valid-user
  </LimitExcept> 

</Location>


EOF

    with_write_lock { 
        open my $fh, '>', $self->svnconf_file_path('httpd.conf') or die $@;
        print $fh $msg or die $@;
        close $fh or die $@;
    };


}


sub obtain_write_lock {

    return 1;

}

sub release_write_lock {

    return 1;
}


sub autogenerated_file_warning {
    my $self = shift;
    my $msg = <<EOF;

# This file was autogenerated by CommitBit @{[$CommitBit::VERSION]} at @{[scalar gmtime]} GMT.
#
# If you edit this file by hand, you risk losing your changes the next
# time CommitBit automatically rewrites this file.
#
# DO NOT EDIT THIS FILE BY HAND.  

EOF

    return $msg;
}

sub run_cmd {
    my $self = shift;
    $self->log->debug("Running: ".join(' ', @_));
    system(@_); 

}



sub current_user_can {
    my $self = shift;
    my $right = shift;
    if ($right eq 'read') { return 1 }
    elsif (($right eq 'create' or $right eq 'update' or $right eq 'delete') and ($self->current_user->user_object &&  $self->current_user->user_object->admin)) {
        return 1;
    }
    return 0

}


1;

