from flask import Flask, render_template, request, redirect, url_for, session, send_file
from io import BytesIO
from authlib.integrations.flask_client import OAuth
from authlib.integrations.base_client.errors import OAuthError, MismatchingStateError
from dotenv import load_dotenv
import os
import re
import requests
import pandas as pd
from calendar import monthrange
from datetime import datetime
from urllib.parse import quote_plus
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.utils import secure_filename
from flask_sqlalchemy import SQLAlchemy

# --------------------------
# Load environment variables
# --------------------------
load_dotenv(os.path.join(os.path.dirname(__file__), '.env'))

# --------------------------
# Flask setup
# --------------------------
APPLICATION_ROOT = os.getenv('APPLICATION_ROOT', '/pytimesheet')
app = Flask(
    __name__,
    static_url_path='/static',
)

db_user = os.getenv('DB_USER', 'timesheet')
db_pass = os.getenv('DB_PASS', '')
db_name = os.getenv('DB_NAME', 'timesheet')
db_host = os.getenv('DB_HOST', 'localhost')
db_port = os.getenv('DB_PORT', '3306')
db_driver = os.getenv('DB_DRIVER', 'pymysql')

encoded_user = quote_plus(db_user)
encoded_pass = quote_plus(db_pass)
encoded_name = quote_plus(db_name)
app.config['SQLALCHEMY_DATABASE_URI'] = (
    f"mysql+{db_driver}://{encoded_user}:{encoded_pass}@{db_host}:{db_port}/{encoded_name}"
)
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
app.config['PREFERRED_URL_SCHEME'] = 'https'
app.config['APPLICATION_ROOT'] = APPLICATION_ROOT

db = SQLAlchemy(app)


class TimesheetEntry(db.Model):
    __tablename__ = "timesheet_entries"

    id = db.Column(db.Integer, primary_key=True)
    assignee = db.Column(db.String(255), nullable=False, index=True)
    month = db.Column(db.String(50), nullable=False)
    year = db.Column(db.Integer, nullable=False, index=True)
    issue_key = db.Column(db.String(100), nullable=True, index=True)
    summary = db.Column(db.Text, nullable=True)
    work_date = db.Column(db.Date, nullable=False, index=True)
    hours = db.Column(db.Float, nullable=False, default=0.0)
    source_file = db.Column(db.String(255), nullable=True)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)


class ManualTaskEntry(db.Model):
    __tablename__ = "manual_task_entries"

    id = db.Column(db.Integer, primary_key=True)
    assignee = db.Column(db.String(255), nullable=False, index=True)
    project = db.Column(db.String(255), nullable=False)
    summary = db.Column(db.String(500), nullable=False)
    description = db.Column(db.Text, nullable=True)
    work_date = db.Column(db.Date, nullable=False, index=True)
    time_spent_hours = db.Column(db.Float, nullable=False, default=0.0)
    created_at = db.Column(db.DateTime, default=datetime.utcnow)
    created_by = db.Column(db.String(255), nullable=True)


with app.app_context():
    db.create_all()

app.secret_key = os.getenv("SECRET_KEY")

# Fix for HTTPS behind Apache reverse proxy
app.wsgi_app = ProxyFix(app.wsgi_app, x_proto=1, x_host=1)

oauth = OAuth(app)

# --------------------------
# Google OAuth (OpenID discovery for id_token / jwks)
# --------------------------
google = oauth.register(
    name='google',
    client_id=os.getenv("GOOGLE_CLIENT_ID"),
    client_secret=os.getenv("GOOGLE_CLIENT_SECRET"),
    server_metadata_url='https://accounts.google.com/.well-known/openid-configuration',
    client_kwargs={
        'scope': 'email profile',
    },
)

CANONICAL_HOST = os.getenv('CANONICAL_HOST', 'peoplehub.golsh2e.com')

# --------------------------
# Assignee Mapping
# --------------------------
assignee_map = {
    "Akhil S": "",
    "Awadhesh": "5daecffeb86cd40c2da5b814",
    "Monish B": "712020:dab8b27b-72b1-487b-8601-9891f845e1d7",
    "Nikhil B": "712020:b8137e7f-a9eb-4e2c-9334-c553234d7851",
    "Priyanka S": "712020:f5e4004d-0a88-455b-b6e7-a8673d0deb9b",
    "Ramkrishan D": "712020:6abe06b5-3b14-4c48-b7c1-0b6dbb0e8f9a",
    "Sakshi J": "712020:2055ac99-ebe7-4220-b4a6-0f00c31c794c",
    "Shambhu K": "712020:12c64083-13c9-4143-8c89-f2abdb6884f0",
    "Shamsher S": "712020:b8f39c86-b3b3-4417-8802-65e422e7b1a1",
    "Sufiyan A": "712020:adebb062-e0f1-4b12-be4a-23fa16640c67",
    "Anugrah": "",
    "Vikas": "",
}

month_name_map = {
    "January": 1, "February": 2, "March": 3, "April": 4,
    "May": 5, "June": 6, "July": 7, "August": 8,
    "September": 9, "October": 10, "November": 11, "December": 12,
}

# Optional: map Google email to exact assignee name (overrides auto-match)
email_assignee_map = {
    "priyanka.sangam@golsh2e.com": "Priyanka S"
}

def _admin_emails():
    raw = os.getenv("ADMIN_EMAILS", "priyanka.sangam@golsh2e.com")
    return {email.strip().lower() for email in raw.split(",") if email.strip()}


def is_admin(user):
    if not user:
        return False
    email = (user.get("email") or "").lower().strip()
    return email in _admin_emails()

JIRA_SERVER = os.getenv("JIRA_SERVER", "https://golsteam.atlassian.net")
JIRA_EMAIL = os.getenv("JIRA_EMAIL")
JIRA_API_TOKEN = os.getenv("JIRA_TOKEN")


def _jira_auth():
    return (JIRA_EMAIL, JIRA_API_TOKEN)

# Raw list of assignees allowed to add manual tasks. Prefer configuring
# this via the MANUAL_TASK_ASSIGNEES environment variable as a
# comma-separated list (e.g. MANUAL_TASK_ASSIGNEES="Anugrah,Vikas,Akhil S,Akhil").
# If the env var is not set, fall back to the built-in defaults.
raw_env = os.getenv('MANUAL_TASK_ASSIGNEES')
if raw_env:
    RAW_MANUAL_TASK_ASSIGNEES = {s.strip() for s in raw_env.split(',') if s.strip()}
else:
    RAW_MANUAL_TASK_ASSIGNEES = {"Anugrah", "Vikas", "Akhil S", "Akhil"}


def _normalize_assignee_name(name):
    if not name:
        return ""
    name = str(name).strip()
    if not name:
        return ""
    aliases = {
        "akhil": "Akhil S",
        "akhil s": "Akhil S",
        "anugrah": "Anugrah",
        "vikas": "Vikas",
    }
    return aliases.get(name.lower(), name)


# Compute canonical MANUAL_TASK_ASSIGNEES from raw list using normalizer
MANUAL_TASK_ASSIGNEES = {
    _normalize_assignee_name(n) for n in RAW_MANUAL_TASK_ASSIGNEES if _normalize_assignee_name(n)
}


def resolve_assignee_from_user(user):
    """Match logged-in Google profile to a team assignee name."""
    if not user:
        return None

    email = (user.get("email") or "").lower().strip()
    if email in email_assignee_map:
        return email_assignee_map[email]

    name = (user.get("name") or "").lower()
    email_local = email.split("@")[0] if email else ""

    for assignee in assignee_map:
        token = assignee.lower().split()[0]
        if token in name or token in email_local.replace(".", " "):
            return assignee

    return None


def extract_adf_text(value):
    """Convert Jira rich-text (ADF) or plain strings to readable text."""
    if value is None:
        return ""
    if isinstance(value, str):
        return value.strip()

    texts = []

    def walk(node):
        if isinstance(node, dict):
            if node.get("type") == "text":
                texts.append(node.get("text", ""))
            for child in node.get("content") or []:
                walk(child)
        elif isinstance(node, list):
            for item in node:
                walk(item)

    walk(value)
    return " ".join(texts).strip()


def search_jira_issues(jql):
    url = f"{JIRA_SERVER}/rest/api/3/search/jql"
    params = {
        "jql": jql,
        "maxResults": 1000,
        "fields": "summary,project,assignee,description",
    }
    response = requests.get(
        url,
        params=params,
        auth=_jira_auth(),
        headers={"Accept": "application/json"},
        timeout=60,
    )
    response.raise_for_status()
    return response.json()["issues"]


def get_issue_worklogs(issue_key):
    url = f"{JIRA_SERVER}/rest/api/3/issue/{issue_key}/worklog"
    worklogs = []
    start_at = 0
    while True:
        response = requests.get(
            url,
            params={"startAt": start_at, "maxResults": 100},
            auth=_jira_auth(),
            headers={"Accept": "application/json"},
            timeout=60,
        )
        response.raise_for_status()
        payload = response.json()
        worklogs.extend(payload.get("worklogs", []))
        if start_at + payload.get("maxResults", 0) >= payload.get("total", 0):
            break
        start_at += payload.get("maxResults", 100)
    return worklogs


def generate_manual_task_report(assignee_name, selected_month, year):
    normalized_name = _normalize_assignee_name(assignee_name)
    if normalized_name not in MANUAL_TASK_ASSIGNEES:
        return None

    month = month_name_map.get(selected_month)
    if not month:
        return None

    start_date = f"{year}-{month:02d}-01"
    end_day = monthrange(year, month)[1]
    end_date = f"{year}-{month:02d}-{end_day}"
    all_dates = pd.date_range(start=start_date, end=end_date).strftime("%d-%b").tolist()

    start_dt = datetime.strptime(start_date, "%Y-%m-%d").date()
    end_dt = datetime.strptime(end_date, "%Y-%m-%d").date()
    entries = (
        ManualTaskEntry.query.filter(
            ManualTaskEntry.assignee == normalized_name,
            ManualTaskEntry.work_date >= start_dt,
            ManualTaskEntry.work_date <= end_dt,
        )
        .order_by(ManualTaskEntry.work_date.asc(), ManualTaskEntry.id.asc())
        .all()
    )
    if not entries:
        return None

    rows = []
    for entry in entries:
        rows.append({
            "Assignee": entry.assignee,
            "Project": entry.project,
            "Summary": entry.summary,
            "Description": entry.description or "",
            "Date": entry.work_date.strftime("%d-%b"),
            "Time Spent (h)": entry.time_spent_hours,
        })

    df = pd.DataFrame(rows)
    pivot = df.pivot_table(
        index=["Assignee", "Project", "Summary", "Description"],
        columns="Date",
        values="Time Spent (h)",
        aggfunc="sum",
        fill_value=0,
    )
    pivot.columns.name = None
    pivot = pivot.reset_index().round(2).fillna("")

    for date in all_dates:
        if date not in pivot.columns:
            pivot[date] = ""

    pivot = pivot[["Assignee", "Project", "Summary", "Description"] + all_dates]

    pivot_for_sum = pivot.replace("", 0)
    for col in all_dates:
        pivot_for_sum[col] = pd.to_numeric(pivot_for_sum[col], errors="coerce").fillna(0)
    total_row_values = pivot_for_sum[all_dates].sum().round(1).tolist()

    total_row = ["Total (per day)", "", "", ""] + total_row_values
    total_row_df = pd.DataFrame([total_row], columns=pivot.columns)

    monthly_total_value = round(sum(total_row_values), 1)
    monthly_total_row = ["Total Monthly Hours", "", "", ""] + [""] * (len(all_dates) - 1) + [monthly_total_value]
    monthly_total_df = pd.DataFrame([monthly_total_row], columns=pivot.columns)

    pivot_display = pivot.replace(0.0, "")
    return pd.concat([pivot_display, total_row_df, monthly_total_df], ignore_index=True)


def generate_jira_worklog_report(assignee_name, selected_month, year):
    assignee_name = _normalize_assignee_name(assignee_name)
    if assignee_name in MANUAL_TASK_ASSIGNEES:
        return generate_manual_task_report(assignee_name, selected_month, year)

    assignee_id = assignee_map.get(assignee_name)
    if not assignee_id:
        return None

    month = month_name_map.get(selected_month)
    if not month:
        return None

    start_date = f"{year}-{month:02d}-01"
    end_day = monthrange(year, month)[1]
    end_date = f"{year}-{month:02d}-{end_day}"
    all_dates = pd.date_range(start=start_date, end=end_date).strftime("%d-%b").tolist()

    jql = (
        f'assignee = {assignee_id} '
        f'AND worklogDate >= "{start_date}" '
        f'AND worklogDate <= "{end_date}" '
        f"ORDER BY created ASC"
    )

    issues = search_jira_issues(jql)
    data = []

    for issue in issues:
        fields = issue.get("fields", {})
        summary = fields.get("summary", "")
        project = fields.get("project", {}).get("name", "")
        issue_description = extract_adf_text(fields.get("description"))
        assignee_field = fields.get("assignee") or {}
        assignee_display = assignee_field.get("displayName") or assignee_name
        key = issue["key"]

        for log in get_issue_worklogs(key):
            author = log.get("author") or {}
            if author.get("accountId") != assignee_id:
                continue

            started = (log.get("started") or "")[:10]
            if not started or started < start_date or started > end_date:
                continue

            date_str = datetime.strptime(started, "%Y-%m-%d").strftime("%d-%b")
            time_spent_hrs = round(log.get("timeSpentSeconds", 0) / 3600, 2)
            worklog_comment = extract_adf_text(log.get("comment"))
            description = worklog_comment or issue_description

            data.append({
                "Assignee": assignee_display,
                "Project": project,
                "Summary": summary,
                "Description": description,
                "Date": date_str,
                "Time Spent (h)": time_spent_hrs,
            })

    df = pd.DataFrame(data)
    if df.empty:
        return None

    pivot = df.pivot_table(
        index=["Assignee", "Project", "Summary", "Description"],
        columns="Date",
        values="Time Spent (h)",
        aggfunc="sum",
        fill_value=0,
    )
    pivot.columns.name = None
    pivot = pivot.reset_index().round(2).fillna("")

    for date in all_dates:
        if date not in pivot.columns:
            pivot[date] = ""

    pivot = pivot[["Assignee", "Project", "Summary", "Description"] + all_dates]

    pivot_for_sum = pivot.replace("", 0)
    for col in all_dates:
        pivot_for_sum[col] = pd.to_numeric(pivot_for_sum[col], errors="coerce").fillna(0)
    total_row_values = pivot_for_sum[all_dates].sum().round(1).tolist()

    total_row = ["Total (per day)", "", "", ""] + total_row_values
    total_row_df = pd.DataFrame([total_row], columns=pivot.columns)

    monthly_total_value = round(sum(total_row_values), 1)
    monthly_total_row = (
        ["Total Monthly Hours", "", "", ""]
        + [""] * (len(all_dates) - 1)
        + [monthly_total_value]
    )
    monthly_total_df = pd.DataFrame([monthly_total_row], columns=pivot.columns)

    pivot_display = pivot.replace(0.0, "")
    return pd.concat([pivot_display, total_row_df, monthly_total_df], ignore_index=True)


def build_excel_download(df, assignee_name, month, year):
    buffer = BytesIO()
    with pd.ExcelWriter(buffer, engine="openpyxl") as writer:
        df.to_excel(writer, index=False, sheet_name="Timesheet")
    buffer.seek(0)
    safe_name = f"{assignee_name}_{month}_{year}".replace(" ", "_")
    filename = f"Timesheet_{safe_name}.xlsx"
    return buffer, filename


@app.before_request
def ensure_canonical_host():
    """Keep OAuth session/callback on one hostname when needed."""
    if request.path.startswith('/static'):
        return None

    host = request.host.split(':', 1)[0]
    if host in {CANONICAL_HOST, 'localhost', '127.0.0.1'}:
        return None

    return redirect(
        f'https://{CANONICAL_HOST}{request.full_path}',
        code=302,
    )


def _app_path(path):
    prefix = (app.config.get('APPLICATION_ROOT') or '').rstrip('/')
    if not prefix:
        return path
    return f"{prefix}{path}" if path.startswith('/') else f"{prefix}/{path}"


def _oauth_callback_url():
    # Prefer an explicit redirect URI from env (useful for exact Google Console entry)
    env_redirect = os.getenv('GOOGLE_REDIRECT_URI')
    if env_redirect:
        # If env_redirect points to the application root (e.g. https://host/pytimesheet),
        # append the /auth/callback path. Otherwise assume it's a full callback URL.
        if env_redirect.rstrip('/').endswith('/pytimesheet'):
            return env_redirect.rstrip('/') + '/auth/callback'
        return env_redirect

    return f'https://{CANONICAL_HOST}{_app_path("/auth/callback")}'


def _exchange_google_code():
    """Exchange auth code for token without parsing id_token (avoids jwks errors)."""
    params = {
        'code': request.args.get('code'),
        'state': request.args.get('state'),
    }
    state_data = google.framework.get_state_data(session, params.get('state'))
    params = google._format_state_params(state_data, params)
    return google.fetch_access_token(**params)


def _fetch_google_user(token):
    user_info = token.get('userinfo')
    if user_info:
        return user_info
    resp = google.get(
        'https://www.googleapis.com/oauth2/v3/userinfo',
        token=token,
    )
    resp.raise_for_status()
    return resp.json()


def authenticate_local_user(username, password):
    """Validate username/password login for team members."""
    expected_password = os.getenv("LOCAL_LOGIN_PASSWORD")
    if not expected_password or password != expected_password:
        return None

    key = username.lower().strip()
    if key in ("admin", "priyanka", "priyanka s"):
        return {"name": "Priyanka S", "email": "priyanka.sangam@gols.in"}

    for assignee in assignee_map:
        token = assignee.lower().split()[0]
        if key in (token, assignee.lower()):
            email = next(
                (e for e, a in email_assignee_map.items() if a == assignee),
                f"{token}@gols.in",
            )
            return {"name": assignee, "email": email}

    return None


def _first_non_empty(row, candidates):
    for name in candidates:
        if name in row.index:
            value = row[name]
            if pd.isna(value):
                continue
            value = str(value).strip()
            if value:
                return value
    return ""


def _date_columns_from_frame(df):
    columns = []
    for column in df.columns:
        if column is None:
            continue
        text = str(column).strip()
        if re.match(r"^\d{1,2}[-/]([A-Za-z]{3,9}|\d{1,2})$", text):
            columns.append(text)
    return columns


def _parse_work_date(day_label, year):
    raw = str(day_label).strip()
    for fmt in ("%d-%b", "%d-%B", "%d-%m"):
        try:
            return datetime.strptime(raw, fmt).replace(year=year).date()
        except ValueError:
            continue
    return None


def import_excel_timesheet(file_storage, assignee, month, year, source_name):
    upload_path = os.path.join(app.instance_path, secure_filename(source_name))
    os.makedirs(app.instance_path, exist_ok=True)
    file_storage.save(upload_path)

    df = pd.read_excel(upload_path)
    if df.empty:
        return 0

    date_columns = _date_columns_from_frame(df)
    if not date_columns:
        raise ValueError("No day columns like 01-Feb were found in the uploaded workbook")

    year_int = int(year)
    month_name = month
    created_count = 0

    for _, row in df.iterrows():
        issue_key = _first_non_empty(row, ["Issue Key", "Issue key", "Issue", "Key"])
        summary = _first_non_empty(row, ["Summary", "Task Summary", "Title"])

        for day_column in date_columns:
            value = row[day_column]
            if pd.isna(value):
                continue
            try:
                hours = float(value)
            except (TypeError, ValueError):
                continue
            if hours <= 0:
                continue

            work_date = _parse_work_date(day_column, year_int)
            if not work_date:
                continue

            record = TimesheetEntry(
                assignee=assignee,
                month=month_name,
                year=year_int,
                issue_key=issue_key,
                summary=summary,
                work_date=work_date,
                hours=hours,
                source_file=source_name,
            )
            db.session.add(record)
            created_count += 1

    if created_count:
        db.session.commit()
    else:
        db.session.rollback()

    return created_count


# --------------------------
# Routes
# --------------------------
@app.route("/", methods=["GET", "POST"])
@app.route("/pytimesheet", methods=["GET", "POST"])
@app.route("/pytimesheet/", methods=["GET", "POST"])
def index():
    if session.get('user'):
        return redirect(url_for('getSheet'))

    if request.method == "POST":
        username = request.form.get("username", "").strip()
        password = request.form.get("password", "")
        user = authenticate_local_user(username, password)
        if user:
            session["user"] = user
            return redirect(url_for("getSheet"))
        return render_template(
            "index.html",
            user=None,
            error="Invalid username or password.",
        )

    return render_template("index.html", user=None)


@app.route('/google-login')
@app.route('/pytimesheet/google-login')
def login():
    callback_url = _oauth_callback_url()
    return google.authorize_redirect(callback_url)


@app.route('/auth/callback')
@app.route('/pytimesheet/auth/callback')
def callback():
    oauth_error = request.args.get('error')
    if oauth_error:
        description = request.args.get('error_description', oauth_error)
        return render_template(
            "index.html", user=None, error=f"Google sign-in failed: {description}"
        ), 400

    try:
        token = _exchange_google_code()
        session['user'] = _fetch_google_user(token)
        return redirect(url_for('getSheet'))
    except MismatchingStateError:
        return render_template(
            "index.html",
            user=None,
            error=f"Sign-in session expired. Open the site at https://{CANONICAL_HOST} and try again.",
        ), 400
    except OAuthError as e:
        return render_template(
            "index.html",
            user=None,
            error=f"Google sign-in failed: {e.description or e.error}",
        ), 400
    except Exception:
        app.logger.exception("OAuth callback failed")
        return render_template(
            "index.html",
            user=None,
            error="Sign-in failed. Please try again.",
        ), 500


@app.route('/logout')
@app.route('/pytimesheet/logout')
def logout():
    session.pop('user', None)
    return redirect(url_for('index'))


@app.route('/add-task', methods=['GET', 'POST'])
@app.route('/pytimesheet/add-task', methods=['GET', 'POST'])
def add_task():
    if not session.get('user'):
        return redirect(url_for('index'))

    user = session.get('user')
    admin = is_admin(user)
    default_assignee = resolve_assignee_from_user(user) or ""

    # Restrict access: only admins or configured MANUAL_TASK_ASSIGNEES
    normalized_default = _normalize_assignee_name(default_assignee)
    if not admin and normalized_default not in MANUAL_TASK_ASSIGNEES:
        return render_template(
            'form.html',
            **_form_context(
                user,
                error='You are not authorized to add manual tasks.',
            ),
        )

    if request.method == 'POST':
        assignee = (request.form.get('assignee') or '').strip()
        project = (request.form.get('project') or '').strip()
        summary = (request.form.get('summary') or '').strip()
        description = (request.form.get('description') or '').strip()
        work_date = (request.form.get('work_date') or '').strip()
        time_spent = (request.form.get('time_spent') or '').strip()

        if not assignee or not project or not summary or not work_date or not time_spent:
            return render_template(
                'add_task.html',
                user=user,
                is_admin=admin,
                assignees=list(assignee_map.keys()),
                default_assignee=default_assignee,
                error='Please fill all required fields.',
                today=datetime.now().strftime('%Y-%m-%d'),
            )

        try:
            parsed_date = datetime.strptime(work_date, '%Y-%m-%d').date()
            parsed_hours = float(time_spent)
        except ValueError:
            return render_template(
                'add_task.html',
                user=user,
                is_admin=admin,
                assignees=list(assignee_map.keys()),
                default_assignee=default_assignee,
                error='Please enter a valid date and numeric hours.',
                today=datetime.now().strftime('%Y-%m-%d'),
            )

        entry = ManualTaskEntry(
            assignee=assignee,
            project=project,
            summary=summary,
            description=description,
            work_date=parsed_date,
            time_spent_hours=parsed_hours,
            created_by=user.get('email'),
        )
        db.session.add(entry)
        db.session.commit()

        return render_template(
            'add_task.html',
            user=user,
            is_admin=admin,
            assignees=list(assignee_map.keys()),
            default_assignee=default_assignee,
            success='Task saved successfully.',
            today=datetime.now().strftime('%Y-%m-%d'),
        )

    return render_template(
        'add_task.html',
        user=user,
        is_admin=admin,
        assignees=list(assignee_map.keys()),
        default_assignee=default_assignee,
        today=datetime.now().strftime('%Y-%m-%d'),
    )


@app.route('/download-task-excel')
@app.route('/pytimesheet/download-task-excel')
def download_task_excel():
    if not session.get('user'):
        return redirect(url_for('index'))

    entries = ManualTaskEntry.query.order_by(ManualTaskEntry.work_date.asc(), ManualTaskEntry.id.asc()).all()
    if not entries:
        return redirect(url_for('add_task'))

    rows = [
        {
            'Assignee': entry.assignee,
            'Project': entry.project,
            'Summary': entry.summary,
            'Description': entry.description or '',
            'Date': entry.work_date.strftime('%d-%b'),
            'Time Spent (h)': entry.time_spent_hours,
        }
        for entry in entries
    ]
    df = pd.DataFrame(rows)

    buffer = BytesIO()
    with pd.ExcelWriter(buffer, engine='openpyxl') as writer:
        df.to_excel(writer, index=False, sheet_name='Manual Tasks')
    buffer.seek(0)

    return send_file(
        buffer,
        as_attachment=True,
        download_name='Manual_Tasks.xlsx',
        mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
    )


@app.route('/import-timesheet', methods=['GET', 'POST'])
@app.route('/pytimesheet/import-timesheet', methods=['GET', 'POST'])
def import_timesheet():
    if not session.get('user'):
        return redirect(url_for('index'))

    user = session.get('user')
    if request.method == 'POST':
        assignee = (request.form.get('assignee') or '').strip()
        month = (request.form.get('month') or '').strip()
        year = (request.form.get('year') or '').strip()
        file_storage = request.files.get('excel_file')

        if not assignee or not month or not year or not file_storage or file_storage.filename == '':
            return render_template(
                'import_timesheet.html',
                user=user,
                assignees=list(assignee_map.keys()),
                months=list(month_name_map.keys()),
                current_year=datetime.now().year,
                error='Please fill all fields and choose an Excel file.',
            )

        try:
            created_count = import_excel_timesheet(
                file_storage,
                assignee=assignee,
                month=month,
                year=year,
                source_name=file_storage.filename,
            )
            return render_template(
                'import_timesheet.html',
                user=user,
                assignees=list(assignee_map.keys()),
                months=list(month_name_map.keys()),
                current_year=datetime.now().year,
                success=f'Imported {created_count} entries into the timesheet database.',
            )
        except Exception as exc:
            app.logger.exception('Excel import failed')
            return render_template(
                'import_timesheet.html',
                user=user,
                assignees=list(assignee_map.keys()),
                months=list(month_name_map.keys()),
                current_year=datetime.now().year,
                error=f'Import failed: {exc}',
            )

    return render_template(
        'import_timesheet.html',
        user=user,
        assignees=list(assignee_map.keys()),
        months=list(month_name_map.keys()),
        current_year=datetime.now().year,
    )


def _form_context(user, **kwargs):
    admin = is_admin(user)
    assignee = resolve_assignee_from_user(user)
    normalized = _normalize_assignee_name(assignee)
    show_manual_add = normalized in MANUAL_TASK_ASSIGNEES
    return {
        "assignees": list(assignee_map.keys()),
        "months": list(month_name_map.keys()),
        "user": user,
        "is_admin": admin,
        "default_assignee": assignee,
        "show_manual_add": show_manual_add,
        **kwargs,
    }


@app.route("/get-timesheet", methods=["GET", "POST"])
@app.route("/pytimesheet/get-timesheet", methods=["GET", "POST"])
def getSheet():
    if not session.get('user'):
        return redirect(url_for('index'))

    current_year = datetime.now().year
    user = session.get("user")
    admin = is_admin(user)
    user_assignee = resolve_assignee_from_user(user)

    if not admin and not user_assignee:
        return render_template(
            "form.html",
            **_form_context(
                user,
                error="Your Google account is not linked to a team member. Contact admin.",
                current_year=current_year,
            ),
        )

    if request.method == "POST":
        selected_month = request.form.get("month")
        selected_year = request.form.get("year")
        if admin:
            selected_assignee = request.form.get("assignee")
        else:
            selected_assignee = user_assignee

        try:
            year_int = int(selected_year) if selected_year else current_year
            df = generate_jira_worklog_report(
                selected_assignee, selected_month, year_int
            )

            if df is None or df.empty:
                return render_template(
                    "form.html",
                    **_form_context(
                        user,
                        error=(
                            f"No data found for {selected_assignee} "
                            f"({selected_month} {year_int})"
                        ),
                        current_year=year_int,
                        default_assignee=selected_assignee,
                    ),
                )

            session["report_params"] = {
                "assignee": selected_assignee,
                "month": selected_month,
                "year": year_int,
            }
            return render_template(
                "form.html",
                **_form_context(
                    user,
                    headers=df.columns.tolist(),
                    table_data=df.values.tolist(),
                    current_year=year_int,
                    default_assignee=selected_assignee,
                    selected_month=selected_month,
                    selected_year=year_int,
                ),
            )

        except Exception as e:
            import traceback
            traceback.print_exc()
            return render_template(
                "form.html",
                **_form_context(
                    user,
                    error=f"System Error: {str(e)}",
                    current_year=current_year,
                    default_assignee=selected_assignee or user_assignee,
                ),
            )

    return render_template(
        "form.html",
        **_form_context(user, current_year=current_year),
    )


@app.route("/download-timesheet")
@app.route("/pytimesheet/download-timesheet")
def download_timesheet():
    if not session.get("user"):
        return redirect(url_for("index"))

    params = session.get("report_params")
    if not params:
        return redirect(url_for("getSheet"))

    df = generate_jira_worklog_report(
        params["assignee"], params["month"], params["year"]
    )
    if df is None or df.empty:
        return redirect(url_for("getSheet"))

    buffer, filename = build_excel_download(
        df, params["assignee"], params["month"], params["year"]
    )
    return send_file(
        buffer,
        as_attachment=True,
        download_name=filename,
        mimetype=(
            "application/vnd.openxmlformats-officedocument"
            ".spreadsheetml.sheet"
        ),
    )


if __name__ == "__main__":
    app.run(debug=True)
