"""
models.py
---------
Every database table in the system, defined as a Python class.

WHY ONE FILE (for now): with a schema this size it's tempting to split
models across many files, but that adds import complexity for not much
benefit at this stage. Everything is easy to find in one place. If this
file gets unwieldy later, it can be split by concern (competitions.py,
matches.py, etc.) without changing how anything else in the app uses them.

NOTE ON MULTI-CLUB FUTURE: right now this template runs ONE club per
installation (per our plan: each club = its own folder + own database
file). That means there is deliberately NO "club_id" column anywhere below
— a club's entire database file only ever contains that club's own data.
This keeps queries simple and guarantees one club can never accidentally
see another club's data.
"""

from datetime import datetime
from flask_login import UserMixin
from app.extensions import db


class User(db.Model, UserMixin):
    """
    A single table for BOTH admins and players.

    Why one table instead of two? An admin at a golf club is very often
    ALSO a player who wants to enter competitions. Keeping them in one
    table with a `role` column avoids duplicate records and awkward
    "which table is this person in" logic elsewhere in the app.

    UserMixin (from Flask-Login) adds the methods Flask-Login needs
    (is_authenticated, get_id, etc.) so we don't have to write them.
    """
    __tablename__ = "users"

    id = db.Column(db.Integer, primary_key=True)

    # Basic contact/profile info
    name = db.Column(db.String(120), nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    phone = db.Column(db.String(30))

    # Golf-specific info, used when generating draws / entries
    handicap = db.Column(db.Float)
    gender = db.Column(db.String(10))  # "male" / "female" — used to filter which competitions apply

    # Login
    password_hash = db.Column(db.String(255), nullable=False)

    # "admin" can access the admin section; "player" cannot.
    # A club can have more than one admin (e.g. club secretary + competition manager).
    role = db.Column(db.String(10), nullable=False, default="player")  # "admin" or "player"

    is_active_member = db.Column(db.Boolean, default=True)  # lets admin deactivate someone without deleting history
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    def is_admin(self):
        """Small helper so templates/routes can write `if user.is_admin()` instead of comparing strings everywhere."""
        return self.role == "admin"


class CompetitionTemplate(db.Model):
    """
    The fixed list of competition TYPES a club can choose from when they
    set up a new competition (Matchplay Singles - Men, etc.)

    This table is seeded once (see seed_data.py) and clubs don't edit it
    directly — they just pick from it when creating a Competition.
    """
    __tablename__ = "competition_templates"

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)  # e.g. "Matchplay Singles - Men"

    # "singles" or "doubles" — controls whether Entries need a partner
    format_type = db.Column(db.String(10), nullable=False)

    # "men", "ladies", or "mixed" — used to filter eligible players at signup
    gender_category = db.Column(db.String(10), nullable=False)


class Competition(db.Model):
    """
    A club's actual running instance of a competition (Stage 1 fields, per
    our plan): registration window, dates, entry fee, competition manager.

    `stage` tracks where this competition currently is in its lifecycle:
      - "registration"  : Stage 1, sign-up form is open (or not yet open)
      - "draw_pending"   : registration closed, draw not yet generated
      - "draw_review"    : draw generated, admin can still adjust it
      - "live"           : draw finalized and issued, matches underway
      - "complete"       : competition finished
    """
    __tablename__ = "competitions"

    id = db.Column(db.Integer, primary_key=True)
    template_id = db.Column(db.Integer, db.ForeignKey("competition_templates.id"), nullable=False)
    template = db.relationship("CompetitionTemplate")

    # Stage 1: registration window
    registration_opens = db.Column(db.Date, nullable=False)
    registration_closes = db.Column(db.Date, nullable=False)
    entry_fee = db.Column(db.Float, default=0)

    # Competition dates (the window matches are expected to be played within)
    competition_starts = db.Column(db.Date, nullable=False)
    competition_ends = db.Column(db.Date, nullable=False)

    # Competition manager contact — shown to players on the signup form
    manager_name = db.Column(db.String(120))
    manager_contact = db.Column(db.String(120))

    stage = db.Column(db.String(20), nullable=False, default="registration")
    created_at = db.Column(db.DateTime, default=datetime.utcnow)

    entries = db.relationship("Entry", backref="competition", lazy=True)


class Entry(db.Model):
    """
    One player's (or one pair's) signup into a specific Competition.

    partner_id is nullable so this same table covers both cases we agreed
    on for doubles:
      - a pair signs up together        -> partner_id filled in immediately
      - a player signs up alone         -> partner_id is NULL until an
                                           admin pairs them with someone
                                           else during the draw stage
    For singles competitions, partner_id is simply always NULL.
    """
    __tablename__ = "entries"

    id = db.Column(db.Integer, primary_key=True)
    competition_id = db.Column(db.Integer, db.ForeignKey("competitions.id"), nullable=False)

    player_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
    player = db.relationship("User", foreign_keys=[player_id])

    partner_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    partner = db.relationship("User", foreign_keys=[partner_id])

    signup_date = db.Column(db.DateTime, default=datetime.utcnow)
    payment_status = db.Column(db.String(20), default="unpaid")  # "unpaid" / "paid" / "waived"

    def display_name(self):
        """Convenience for templates: 'John Smith' for singles, 'John Smith & Jane Doe' for doubles."""
        if self.partner:
            return f"{self.player.name} & {self.partner.name}"
        return self.player.name


class Draw(db.Model):
    """
    Represents ONE generated bracket for a competition. We keep this as its
    own table (rather than just generating matches directly) so that if an
    admin generates a draw, doesn't like it, and regenerates it, we have a
    clean record of that rather than orphaned matches floating around.
    """
    __tablename__ = "draws"

    id = db.Column(db.Integer, primary_key=True)
    competition_id = db.Column(db.Integer, db.ForeignKey("competitions.id"), nullable=False)
    competition = db.relationship("Competition")

    generated_date = db.Column(db.DateTime, default=datetime.utcnow)

    # While False, admin can still drag/edit matches. Once True, it has been
    # issued to players and should be treated as locked (edits become
    # exceptional admin actions rather than routine ones).
    is_finalized = db.Column(db.Boolean, default=False)

    matches = db.relationship("Match", backref="draw", lazy=True)


class Match(db.Model):
    """
    A single matchplay fixture between two Entries (an entry may be a solo
    player or a pair, depending on competition format — Match doesn't need
    to know or care which).
    """
    __tablename__ = "matches"

    id = db.Column(db.Integer, primary_key=True)
    draw_id = db.Column(db.Integer, db.ForeignKey("draws.id"), nullable=False)

    round_number = db.Column(db.Integer, nullable=False)  # 1 = first round, 2 = quarter final, etc.

    entry1_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=True)
    entry2_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=True)
    entry1 = db.relationship("Entry", foreign_keys=[entry1_id])
    entry2 = db.relationship("Entry", foreign_keys=[entry2_id])
    # Both nullable to allow "bye" matches (odd number of entries in a round)
    # where only one side is populated.

    scheduled_by = db.Column(db.Date)  # date the match should be completed by

    # "pending"    : not yet played / reported
    # "reported"   : one or both sides have submitted a result
    # "disputed"   : the two submitted results don't match
    # "confirmed"  : result agreed (either both sides matched, or admin resolved it)
    status = db.Column(db.String(20), default="pending")

    winner_entry_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=True)
    winner = db.relationship("Entry", foreign_keys=[winner_entry_id])

    result_summary = db.Column(db.String(20))  # e.g. "3&2", "2up", "AS" (all square through to a playoff hole, etc.)


class MatchResultSubmission(db.Model):
    """
    A single side's report of what happened in a match. Both players in a
    match submit one of these independently — we don't just trust one
    person's word for it.

    When both sides have submitted and their winner + result_summary agree,
    the Match is automatically marked "confirmed". If they disagree, the
    Match is marked "disputed" and a Dispute record is created for an admin
    to resolve.
    """
    __tablename__ = "match_result_submissions"

    id = db.Column(db.Integer, primary_key=True)
    match_id = db.Column(db.Integer, db.ForeignKey("matches.id"), nullable=False)
    match = db.relationship("Match", backref="submissions")

    submitted_by_entry_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=False)
    submitted_by_entry = db.relationship("Entry", foreign_keys=[submitted_by_entry_id])

    claimed_winner_entry_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=False)
    result_summary = db.Column(db.String(20), nullable=False)  # e.g. "3&2"

    submitted_at = db.Column(db.DateTime, default=datetime.utcnow)


class Dispute(db.Model):
    """
    Raised automatically when two MatchResultSubmissions for the same
    match disagree, OR manually by a player who thinks a confirmed result
    is wrong. An admin works through open disputes and resolves them.
    """
    __tablename__ = "disputes"

    id = db.Column(db.Integer, primary_key=True)
    match_id = db.Column(db.Integer, db.ForeignKey("matches.id"), nullable=False)
    match = db.relationship("Match")

    raised_by_entry_id = db.Column(db.Integer, db.ForeignKey("entries.id"), nullable=False)
    reason = db.Column(db.Text)

    status = db.Column(db.String(20), default="open")  # "open" / "resolved"
    admin_resolution_notes = db.Column(db.Text)
    resolved_by_user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
    resolved_at = db.Column(db.DateTime)

    created_at = db.Column(db.DateTime, default=datetime.utcnow)
