package Mediabot::Channel;

use strict;
use warnings;
use DBI;

sub new {
    my ($class, $args) = @_;
    my $self = {
        id          => $args->{id},
        name        => $args->{name},
        description => $args->{description} || '',
        topic       => $args->{topic},
        tmdb_lang   => $args->{tmdb_lang},
        chanmode    => $args->{chanmode},
        auto_join   => $args->{auto_join},
        key         => $args->{key},
        dbh         => $args->{dbh},     # needed for SQL updates
        irc         => $args->{irc},     # needed for IRC operations
        logger      => $args->{logger},  # optional logger
    };
    bless $self, $class;
    return $self;
}


sub _log {
    my ($self, $level, $msg) = @_;
    $self->{logger}->log($level, $msg) if $self->{logger};
}

sub _execute_update {
    my ($self, $sql, $binds, $label) = @_;

    # A5: prefer ensure_connected() to handle stale dbh after reconnect
    my $dbh = ($self->{db} && $self->{db}->can('ensure_connected'))
        ? $self->{db}->ensure_connected()
        : $self->{dbh};
    return 0 unless $dbh;

    my $sth = $dbh->prepare($sql);

    unless ($sth) {
        $self->_log(1, "$label SQL prepare error: $DBI::errstr Query: $sql");
        return 0;
    }

    unless ($sth->execute(@{$binds || []})) {
        $self->_log(1, "$label SQL execute error: $DBI::errstr Query: $sql");
        $sth->finish;
        return 0;
    }

    my $rows = $sth->rows;
    $sth->finish;
    return $rows;
}


# ------------------------
# GETTERS
# ------------------------

# Get channel ID
sub get_id         { return shift->{id}; }

# Get channel name (e.g. #channel)
sub get_name       { return shift->{name}; }

# Get channel description
sub get_description { return shift->{description}; }

# Get current topic
sub get_topic      { return shift->{topic}; }

# Get TMDB language (e.g. en-US, fr-FR)
sub get_tmdb_lang  { return shift->{tmdb_lang}; }

# Get channel key (password)
sub get_key        { return shift->{key}; }

# Get auto_join status
sub get_auto_join        { return shift->{auto_join}; }

# Get chanmode status
sub get_chanmode        { return shift->{chanmode}; }

# Get user level in this channel
# Get user level in this channel
sub get_user_level {
    my ($self, $nickname) = @_;

    return 0 unless $self->{dbh};
    return 0 unless defined($self->{id}) && defined($nickname) && $nickname ne '';

    my $sql = q{
        SELECT level
        FROM USER
        JOIN USER_CHANNEL USING (id_user)
        WHERE id_channel = ?
          AND nickname = ?
    };

    my $sth = $self->{dbh}->prepare($sql);

    unless ($sth) {
        $self->_log(1, "get_user_level() SQL prepare error: $DBI::errstr Query: $sql");
        return 0;
    }

    unless ($sth->execute($self->{id}, $nickname)) {
        $self->_log(1, "get_user_level() SQL execute error: $DBI::errstr Query: $sql");
        $sth->finish;
        return 0;
    }

    my $level = 0;
    if (my $ref = $sth->fetchrow_hashref) {
        $level = $ref->{level} // 0;
    }

    $sth->finish;
    return $level;
}


# Get user info (level, automode, greet) in this channel
# Get user info (level, automode, greet) in this channel
sub get_user_info {
    my ($self, $nickname) = @_;

    my $info = {
        level    => 0,
        automode => 'None',
        greet    => 'None',
    };

    return $info unless $self->{dbh};
    return $info unless defined($self->{id}) && defined($nickname) && $nickname ne '';

    my $sql = q{
        SELECT level, automode, greet
        FROM USER
        JOIN USER_CHANNEL USING (id_user)
        WHERE id_channel = ?
          AND nickname = ?
    };

    my $sth = $self->{dbh}->prepare($sql);

    unless ($sth) {
        $self->_log(1, "get_user_info() SQL prepare error: $DBI::errstr Query: $sql");
        return $info;
    }

    unless ($sth->execute($self->{id}, $nickname)) {
        $self->_log(1, "get_user_info() SQL execute error: $DBI::errstr Query: $sql");
        $sth->finish;
        return $info;
    }

    if (my $ref = $sth->fetchrow_hashref) {
        $info->{level}    = $ref->{level}    if defined $ref->{level};
        $info->{automode} = $ref->{automode} if defined $ref->{automode};
        $info->{greet}    = $ref->{greet}    if defined $ref->{greet};
    }

    $sth->finish;
    return $info;
}




# ------------------------
# SETTERS (with database update)
# ------------------------

# Set channel topic and update DB
# Set channel topic and update DB
sub set_topic {
    my ($self, $new_topic) = @_;
    return 0 unless defined $new_topic;

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET topic=? WHERE id_channel=?",
        [ $new_topic, $self->{id} ],
        "set_topic()",
    );

    $self->{topic} = $new_topic if $ok;
    return $ok ? 1 : 0;
}


# Set TMDB language and update DB
# Set TMDB language and update DB
sub set_tmdb_lang {
    my ($self, $new_lang) = @_;
    return 0 unless defined $new_lang;

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET tmdb_lang=? WHERE id_channel=?",
        [ $new_lang, $self->{id} ],
        "set_tmdb_lang()",
    );

    $self->{tmdb_lang} = $new_lang if $ok;
    return $ok ? 1 : 0;
}


# Set channel key (password) and update DB
# Set channel key (password) and update DB
sub set_key {
    my ($self, $new_key) = @_;
    return 0 unless defined $new_key;
    # A4: validate key — no spaces, max 64 chars
    if ($new_key ne '' && ($new_key =~ /\s/ || length($new_key) > 64)) {
        $self->_log(1, "set_key(): invalid key rejected (spaces or too long)");
        return 0;
    }

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET `key`=? WHERE id_channel=?",
        [ $new_key, $self->{id} ],
        "set_key()",
    );

    $self->{key} = $new_key if $ok;
    return $ok ? 1 : 0;
}


# Set channel description and update DB
# Set channel description and update DB
sub set_description {
    my ($self, $new_description) = @_;
    return 0 unless defined $new_description;

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET description=? WHERE id_channel=?",
        [ $new_description, $self->{id} ],
        "set_description()",
    );

    $self->{description} = $new_description if $ok;
    return $ok ? 1 : 0;
}


# Set channel mode (chanmode) and update DB
# Set channel mode (chanmode) and update DB
sub set_chanmode {
    my ($self, $new_chanmode) = @_;
    return 0 unless defined $new_chanmode;
    # mb143-B2: validation chanmode IRC. Le regex precedent /^[+-]?[a-zA-Z]+$/
    # n'acceptait qu'UN seul prefix +/- suivi de lettres, rejetant les modes
    # mixtes standards comme "+stn-k" (set s,t,n et unset k) ou "+ms-Lr".
    # Ces formes sont legitimes RFC 2812 / IRCv3. Le nouveau regex accepte
    # une ou plusieurs sequences {+/-}lettres, avec longueur totale plafonnee.
    if ($new_chanmode ne '' && ($new_chanmode !~ /^([+-][a-zA-Z]+)+$/ || length($new_chanmode) > 32)) {
        $self->_log(1, "set_chanmode(): invalid mode rejected");
        return 0;
    }

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET chanmode=? WHERE id_channel=?",
        [ $new_chanmode, $self->{id} ],
        "set_chanmode()",
    );

    $self->{chanmode} = $new_chanmode if $ok;
    return $ok ? 1 : 0;
}


# Set auto_join flag and update DB
# Set auto_join flag and update DB
sub set_auto_join {
    my ($self, $new_auto_join) = @_;
    return 0 unless defined $new_auto_join;

    my $ok = $self->_execute_update(
        "UPDATE CHANNEL SET auto_join=? WHERE id_channel=?",
        [ $new_auto_join, $self->{id} ],
        "set_auto_join()",
    );

    $self->{auto_join} = $new_auto_join if $ok;
    return $ok ? 1 : 0;
}


# ------------------------
# Channel Methods
# ------------------------

sub exists_in_db {
    my ($self) = @_;

    return undef unless $self->{dbh};
    return undef unless defined($self->{name}) && $self->{name} ne '';

    my $sql = "SELECT id_channel FROM CHANNEL WHERE name = ?";
    my $sth = $self->{dbh}->prepare($sql);

    unless ($sth) {
        $self->_log(1, "exists_in_db() SQL prepare error: $DBI::errstr Query: $sql");
        return undef;
    }

    unless ($sth->execute($self->{name})) {
        $self->_log(1, "exists_in_db() SQL execute error: $DBI::errstr Query: $sql");
        $sth->finish;
        return undef;
    }

    my ($id_channel) = $sth->fetchrow_array;
    $sth->finish;

    return $id_channel;
}


sub create_in_db {
    my ($self) = @_;

    return undef unless $self->{dbh};
    return undef unless defined($self->{name}) && $self->{name} ne '';

    my $sql = "INSERT INTO CHANNEL (name, description, auto_join) VALUES (?, ?, ?)";
    my $sth = $self->{dbh}->prepare($sql);

    unless ($sth) {
        $self->_log(1, "create_in_db() SQL prepare error: $DBI::errstr Query: $sql");
        return undef;
    }

    unless ($sth->execute($self->{name}, $self->{description} || $self->{name}, 1)) {
        $self->_log(1, "create_in_db() SQL execute error: $DBI::errstr Query: $sql");
        $sth->finish;
        return undef;
    }

    $sth->finish;

    my $id = $self->{dbh}->last_insert_id(undef, undef, undef, undef);
    $self->{id} = $id if defined $id;

    return $self->{id};
}


=head1 NAME

Mediabot::Channel - Objet représentant un canal IRC dans Mediabot

=head1 SYNOPSIS

    use Mediabot::Channel;

    my $channel = Mediabot::Channel->new({
        id   => 42,
        name => '#france',
        dbh  => $dbh,
        irc  => $irc,
    });

    my $name = $channel->get_name;
    $channel->set_topic("Nouveau topic");
    my $level = $channel->get_user_level('Teuk');

=head1 DESCRIPTION

Ce module fournit une interface orientée objet pour manipuler les canaux IRC gérés par Mediabot.

Il permet de récupérer ou modifier dynamiquement les propriétés stockées en base de données :
nom, topic, description, mot de passe, mode IRC, langue TMDB, auto-join, etc.

Il offre aussi des méthodes utilitaires pour gérer les utilisateurs associés à un canal.

=head1 CONSTRUCTEUR

=head2 new

    my $channel = Mediabot::Channel->new(\%args);

Crée un nouvel objet Mediabot::Channel.

Clés reconnues dans %args :

=over 4

=item * id

ID numérique du canal (id_channel)

=item * name

Nom IRC du canal (e.g. #monchan)

=item * description

Description libre du canal

=item * topic

Topic IRC courant (non synchrone)

=item * tmdb_lang

Langue TMDB liée (ex: 'fr-FR')

=item * chanmode

Modes IRC (ex: +ntk)

=item * auto_join

Booléen : rejoindre automatiquement au démarrage

=item * key

Mot de passe IRC (clé de canal)

=item * dbh

Handle DBI à la base de données MariaDB/MySQL

=item * irc

Objet IRC (Net::Async::IRC)

=back

=head1 ACCESSEURS (GETTERS)

=head2 get_id

Retourne l’ID du canal (id_channel)

=head2 get_name

Retourne le nom du canal (ex: #gwen)

=head2 get_description

Retourne la description actuelle du canal

=head2 get_topic

Retourne le topic enregistré pour ce canal

=head2 get_tmdb_lang

Retourne la langue TMDB associée (ex: en-US)

=head2 get_key

Retourne la clé IRC (mot de passe du canal)

=head2 get_auto_join

Retourne le statut auto_join (0 ou 1)

=head2 get_chanmode

Retourne le chanmode actuel (ex: +nt)

=head2 get_user_level

    my $level = $channel->get_user_level($nickname);

Retourne le niveau d’un utilisateur donné dans ce canal (via USER_CHANNEL)

=head2 get_user_info

    my $info = $channel->get_user_info($nickname);

Retourne une hashref contenant :

    {
        level    => <niveau>,
        automode => <mode>,
        greet    => <greet text>
    }

=head1 MUTATEURS (SETTERS)

Toutes les méthodes suivantes modifient à la fois l’objet *et* la base SQL.

=head2 set_topic

    $channel->set_topic("Bienvenue sur #gwen");

Met à jour le topic IRC dans la base.

=head2 set_tmdb_lang

    $channel->set_tmdb_lang("fr-FR");

Met à jour la langue TMDB associée.

=head2 set_key

    $channel->set_key("s3cret");

Met à jour la clé (mot de passe) IRC du canal.

=head2 set_description

    $channel->set_description("Canal de Gwen et Teuk");

Met à jour la description libre du canal.

=head2 set_chanmode

    $channel->set_chanmode("+ntk");

Met à jour les modes IRC du canal.

=head2 set_auto_join

    $channel->set_auto_join(1);

Définit si le bot rejoint automatiquement ce canal au lancement.

=head1 MÉTHODES CANAL / DB

=head2 exists_in_db

    my $id = $channel->exists_in_db;

Vérifie si le canal existe dans la base (par nom). Retourne l’ID s’il existe, undef sinon.

=head2 create_in_db

    my $id = $channel->create_in_db;

Crée le canal dans la base si nécessaire. Retourne l’ID du canal nouvellement inséré.

=head1 AUTEUR

Christophe L. (Teuk)

=head1 LICENCE

MIT License - Libre réutilisation avec attribution.

=cut

1;