using System.Globalization;
using System.IO;
using System.Windows;
using System.Windows.Controls;
using Microsoft.Win32;
using SMRC.Core;
using SMRC.Data.SQLite;
namespace SMRC.Desktop.Views;

public partial class PensionView : UserControl
{
    private readonly DatabaseSettings settings;
    private readonly Employer employer;
    private readonly PensionLifecycleRepository repo;
    private readonly NestExportRepository exports;
    private readonly ComboBox worker=new(){DisplayMemberPath="Label"},files=new(){DisplayMemberPath="Label"};
    private readonly ComboBox frequency=new(){ItemsSource=Enum.GetValues<PayFrequency>(),SelectedItem=PayFrequency.Monthly};
    private readonly ComboBox nestFrequency=new(){ItemsSource=new[]{"Monthly","Tax Monthly","Weekly","Tax Weekly","Fortnightly","Four Weekly"},SelectedIndex=0};
    private readonly TextBox earnings=new(){Text="0.00"},evidence=new(),nestId=new(),group=new(),paymentSource=new(),letter=new(){AcceptsReturn=true,TextWrapping=TextWrapping.Wrap,MinHeight=180};
    private readonly DatePicker on=new(){SelectedDate=DateTime.Today},spa=new(),until=new(),membershipDate=new(),letterDate=new(),anniversary=new(),start=new(),end=new(),due=new();
    private readonly CheckBox uk=new(){Content="Works or ordinarily works in the UK",IsChecked=true},request=new(){Content="Written opt-in / joining request retained"},invalidNotice=new(){Content="Corrected invalid opt-out notice; original notice retained"},recent=new(){Content="Employer includes a worker who left within the last 12 months"},delivered=new(){Content="This letter has actually been given to the worker"},refundPaid=new(){Content="The employee's relief-at-source refund has actually been paid"};
    private readonly TextBlock summary=new(){TextWrapping=TextWrapping.Wrap,Margin=new(0,12,0,12)},status=new(){TextWrapping=TextWrapping.Wrap,Margin=new(0,12,0,12)};
    private PensionMembership? state;
    private sealed record WorkerChoice(Employee Employee,EmploymentInstance Employment,string Label);
    private sealed record FileChoice(int Id,string Label);
    public PensionView():this(new EmployerSqliteRepository().GetAll().SingleOrDefault(e=>e.Id==OpenedEmployerService.GetOpenedEmployerId())??new Employer(),new()){}
    public PensionView(Employer employer,DatabaseSettings settings)
    {
        InitializeComponent();this.employer=employer;this.settings=settings;repo=new(settings);exports=new(settings);
        Title("Workplace pensions",28);Note($"{employer.CompanyName} · 2026/27 · NEST qualifying earnings / relief at source");
        var configuration = new AgentSqliteRepository(settings).GetClientConfiguration(employer.Id);
        if (!configuration.WorkplacePensionEnabled)
        {
            Note("Workplace pensions are disabled for this company. Enable workplace pensions in Company details → Pensions to assess workers and prepare exports. Existing payroll deductions and membership records are retained.");
            return;
        }
        Note($"Provider: {configuration.PensionProvider}");
        anniversary.SelectedDate = EmployerSetupRules.Date(employer.PensionReEnrolmentDate) ?? EmployerSetupRules.Date(employer.PensionStagingDate)?.AddYears(3);
        until.SelectedDate = EmployerSetupRules.Date(employer.PensionDeferUntilDate);
        on.SelectedDateChanged += (_, _) => ApplyCompanySchedule();
        frequency.SelectionChanged += (_, _) => ApplyCompanySchedule();
        ApplyCompanySchedule();
        Note("Assess every pay reference period. Retain official State Pension date evidence. Review exceptional cases before filing.");
        Field("Employment",worker);worker.SelectionChanged+=(_,_)=>Attempt(Load);
        var jobs=new EmploymentInstanceSqliteRepository(new DatabaseConnection(settings));
        worker.ItemsSource=new EmployeeSqliteRepository(settings).GetForEmployer(employer.Id,employer.CompanyName).SelectMany(e=>jobs.GetForPerson(e.Id).Where(j=>j.EmployerId==employer.Id).Select(j=>new WorkerChoice(e,j,$"{e.FullName} · {j.PayrollId}"))).ToList();Workspace.Children.Add(summary);
        Title("1. Assess and enrol",20);Field("Assessment / action date",on);Field("Verified State Pension date",spa);Field("Pay reference period",frequency);Field("Assessable earnings (£)",earnings);Workspace.Children.Add(uk);
        Field("Evidence reference: eligibility, written request, provider receipt or payment",evidence);Buttons(("Assess worker",Assess),("Calculate contribution estimate",Estimate));
        Field("NEST employer reference",nestId);Field("NEST group: qualifying earnings; 5% gross employee / 3% employer",group);Field("NEST payment source",paymentSource);Workspace.Children.Add(request);
        Buttons(("Prepare automatic enrolment",()=>Enrol("AE",false)),("Prepare opt-in",()=>Enrol("OPTIN",false)),("Prepare joining",()=>Enrol("WWQE",false)));
        Field("Postpone until",until);Field("Date notice / enrolment information was actually given",letterDate);Workspace.Children.Add(delivered);
        Buttons(("Record postponement",()=>Change("Postponement",s=>{RequireDelivery();return PensionLifecycle.Postpone(s,Day(until),Day(letterDate),evidence.Text);})));
        Title("2. Membership and worker letters",20);Field("Date active membership was achieved at NEST",membershipDate);
        Buttons(("Export selected enrolment",()=>{Authorise();Export(exports.Enrolment(employer.Id,Selected().Employment.Id),"nest-enrolment.csv");}),("Confirm provider membership",ConfirmProvider));
        Field("Worker letter - review before saving or giving to the worker",letter);Buttons(("Prepare worker letter",PrepareLetter),("Save reviewed letter",SaveLetter),("Record letter delivery",RecordLetterDelivery));
        Title("3. Leaving, refunds and re-enrolment",20);Workspace.Children.Add(invalidNotice);Workspace.Children.Add(refundPaid);
        Buttons(("Record valid opt-out",OptOut),("Record cessation",()=>Change("Cessation",s=>s.Status==PensionMembershipStatus.Active?s with{Status=PensionMembershipStatus.Ceased,EffectiveOn=Day(on),LastLeftOn=Day(on),Evidence=evidence.Text}:throw new InvalidOperationException("Only an active member can cease membership."))),
            ("Record employee refund paid",()=>Change("Refund paid",s=>s.Status==PensionMembershipStatus.OptedOut&&!s.RefundSettled&&refundPaid.IsChecked==true?s with{RefundSettled=true,Evidence=evidence.Text}:throw new InvalidOperationException("Confirm actual payment of an outstanding refund, with evidence."))));
        Field("Third anniversary of duties start / previous re-enrolment",anniversary);Workspace.Children.Add(recent);Buttons(("Prepare re-enrolment",()=>Enrol("AE",true)));
        Title("4. NEST contribution schedule",20);Field("NEST schedule frequency",nestFrequency);Field("Earnings period start",start);Field("Earnings period end",end);Field("Payment due date shown by NEST",due);
        Buttons(("Export finalised group contributions",()=>{Authorise();if(employer.NestAskForPaymentDueDate && MessageBox.Show($"Confirm that NEST shows {Day(due):dd MMM yyyy} as the payment due date.","NEST payment due date",MessageBoxButton.YesNo)!=MessageBoxResult.Yes)return;Export(exports.Contributions(employer.Id,group.Text,new(nestId.Text,paymentSource.Text,(string)nestFrequency.SelectedItem,Day(start),Day(end),Day(due))),"nest-contributions.csv");}));
        Field("Export history",files);Buttons(("Save archived export",()=>{Authorise();if(files.SelectedItem is not FileChoice f)throw new InvalidOperationException("Select an export.");Export((f.Id,exports.Archived(employer.Id,f.Id)),"nest-archived.csv");}),("Record submitted",()=>Result("Submitted")),("Record accepted",()=>Result("Accepted")),("Record rejected",()=>Result("Rejected")),("Record payment confirmed",()=>Result("Payment confirmed")));
        Note("Upload through NEST and retain its response. An export does not establish acceptance or payment. Re-declare compliance with The Pensions Regulator when required.");Workspace.Children.Add(status);RefreshFiles();
    }
    private void ApplyCompanySchedule()
    {
        if (on.SelectedDate is not DateTime payDate || frequency.SelectedItem is not PayFrequency payFrequency) return;
        var period = EmployerSetupRules.NestPeriod(employer, payFrequency, payDate);
        nestFrequency.SelectedItem = period.Frequency; start.SelectedDate = period.Start; end.SelectedDate = period.End;
        due.SelectedDate = null; // The provider's actual due date must be supplied, never invented.
    }
    private void Title(string text,int size)=>Workspace.Children.Add(new TextBlock{Text=text,FontSize=size,FontWeight=FontWeights.Bold,Margin=new(0,18,0,8)});
    private void Note(string text)=>Workspace.Children.Add(new TextBlock{Text=text,TextWrapping=TextWrapping.Wrap,Margin=new(0,4,0,8)});
    private void Field(string label,Control control){Note(label);control.MinHeight=28;control.Margin=new(0,0,0,8);Workspace.Children.Add(control);}
    private void Buttons(params (string Text,Action Action)[] actions){var panel=new WrapPanel();foreach(var a in actions){var b=new Button{Content=a.Text,Padding=new(12,7,12,7),Margin=new(0,0,8,8)};b.Click+=(_,_)=>Attempt(a.Action);panel.Children.Add(b);}Workspace.Children.Add(panel);}
    private void Attempt(Action a){try{a();}catch(Exception e){status.Text=e.Message;}}
    private WorkerChoice Selected()=>worker.SelectedItem as WorkerChoice??throw new InvalidOperationException("Select an employment.");
    private static DateTime Day(DatePicker picker)=>picker.SelectedDate?.Date??throw new InvalidOperationException("Complete the relevant date.");
    private void Authorise(){if(new AgentSqliteRepository(settings).GetPermission(AuditService.CurrentUser).Role==SMRC.Core.Agent.AgentUserRole.ReadOnly)throw new InvalidOperationException("Your role cannot change pension records.");}
    private void Load(){var selected=Selected();state=repo.History(employer.Id,selected.Employment.Id).LastOrDefault();frequency.SelectedItem=selected.Employee.Frequency;if(state?.Input is not null){spa.SelectedDate=state.Input.StatePensionDate;earnings.Text=state.Input.Earnings.ToString("F2");}nestId.Text=state?.NestEmployerReference??"";group.Text=state?.NestGroup??"";paymentSource.Text=state?.NestPaymentSource??"";ShowState();}
    private void ShowState(){summary.Text=state is null?"No pension lifecycle recorded.":$"{state.Status} · {state.Assessment?.Category} · effective {state.EffectiveOn:dd MMM yyyy}\nEnrolment {state.MembershipStart:dd MMM yyyy}; deferral {state.DeferredUntil:dd MMM yyyy}\nEmployee refund £{state.EmployeeRefund:N2} ({(state.RefundSettled?"paid":"unsettled")}); provider employer refund £{state.EmployerRefund:N2}; employee refund due {state.RefundDue:dd MMM yyyy}\nEvidence: {state.Evidence}";}
    private void Change(string action,Func<PensionMembership,PensionMembership> fn){Authorise();state=repo.Change(employer.Id,Selected().Employment.Id,state?.Version??0,action,AuditService.CurrentUser,fn);ShowState();status.Text=action+" recorded.";}
    private void Assess(){var input=new PensionAssessmentInput(Day(on),Selected().Employee.DateOfBirth,Day(spa),uk.IsChecked==true,decimal.Parse(earnings.Text,CultureInfo.GetCultureInfo("en-GB")),(PayFrequency)frequency.SelectedItem,evidence.Text);Change("Assessment",s=>PensionLifecycle.Reassess(s,input));}
    private void Estimate(){if(state?.Assessment is null)throw new InvalidOperationException("Assess the worker first.");var qe=state.Assessment.QualifyingEarnings;status.Text=$"Qualifying earnings £{qe:N2}; standard NEST estimate: employee cash £{Math.Round(Math.Round(qe*.05m,2,MidpointRounding.AwayFromZero)*.8m,2,MidpointRounding.AwayFromZero):N2}, employer £{Math.Round(qe*.03m,2,MidpointRounding.AwayFromZero):N2}. Final payroll uses saved membership and installed rules.";}
    private void Enrol(string type,bool reenrol)=>Change(reenrol?"Re-enrolment":"Enrolment",s=>{
        if(reenrol)PensionLifecycle.ValidateReenrolment(s,Day(anniversary),Day(on),recent.IsChecked==true);else if(type=="AE"&&s.LastLeftOn.HasValue)throw new InvalidOperationException("Use re-enrolment for a former member, or retain their written opt-in request.");
        var next=PensionLifecycle.Enrol(s,type,Day(on),request.IsChecked==true,evidence.Text) with{NestEmployerReference=nestId.Text.Trim(),NestGroup=group.Text.Trim(),NestPaymentSource=paymentSource.Text.Trim()};
        _=NestPensionFiles.Enrolment(next.NestEmployerReference,next.NestGroup,next.NestPaymentSource,Selected().Employee,next);return next;});
    private void ConfirmProvider()=>Change("Provider membership confirmed",s=>{
        if(s.Status!=PensionMembershipStatus.AwaitingProvider||Day(membershipDate)<s.MembershipStart||Day(membershipDate)>DateTime.Today||string.IsNullOrWhiteSpace(evidence.Text))throw new InvalidOperationException("Record actual achieved membership date and NEST confirmation evidence.");
        return s with{Status=PensionMembershipStatus.Active,MembershipAchieved=Day(membershipDate),ProviderReference=evidence.Text,Evidence=evidence.Text};});
    private void RequireDelivery(){if(delivered.IsChecked!=true||Day(letterDate)>DateTime.Today)throw new InvalidOperationException("Confirm the notice was actually given, with its date and evidence.");}
    private void RecordLetterDelivery(){RequireDelivery();if(string.IsNullOrWhiteSpace(letter.Text))throw new InvalidOperationException("Retain the actual letter text.");Authorise();state=repo.RecordLetterDelivery(employer.Id,Selected().Employment.Id,state?.Version??0,AuditService.CurrentUser,Day(letterDate),letter.Text,evidence.Text);ShowState();status.Text="Delivery and letter archived together.";}
    private void PrepareLetter(){if(state is null)throw new InvalidOperationException("Assess the worker first.");letter.Text=PensionLetters.Create(employer,Selected().Employee,state);status.Text="Letter prepared for review. Saving does not record delivery.";}
    private void SaveLetter(){Authorise();if(state is null)throw new InvalidOperationException("Select a pension record.");var dialog=new SaveFileDialog{Filter="Text document|*.txt",FileName="pension-letter.txt"};if(dialog.ShowDialog()!=true)return;File.WriteAllText(dialog.FileName,letter.Text);repo.SaveDocument(employer.Id,Selected().Employment.Id,state.Version,"Reviewed letter",letter.Text,"Saved by "+AuditService.CurrentUser);status.Text="Letter saved and archived; delivery remains separate.";}
    private void OptOut(){if(state?.MembershipStart is null)throw new InvalidOperationException("Select an active membership.");Authorise();state=repo.RecordOptOut(employer.Id,Selected().Employment.Id,state.Version,AuditService.CurrentUser,Day(on),invalidNotice.IsChecked==true,evidence.Text,evidence.Text);ShowState();status.Text="Opt-out and reconciled refund obligation recorded.";}
    private void Export((int Id,byte[] Content) file,string name){var dialog=new SaveFileDialog{Filter="NEST CSV|*.csv",FileName=name};if(dialog.ShowDialog()==true)File.WriteAllBytes(dialog.FileName,file.Content);RefreshFiles();status.Text=$"Export {file.Id} archived. Upload through NEST and record its actual response.";}
    private void RefreshFiles(){files.ItemsSource=exports.Files(employer.Id).Select(f=>new FileChoice(f.Id,f.Label)).ToList();files.SelectedIndex=0;}
    private void Result(string result){Authorise();if(files.SelectedItem is not FileChoice f)throw new InvalidOperationException("Select an export.");exports.RecordResult(employer.Id,f.Id,result,evidence.Text,AuditService.CurrentUser);RefreshFiles();status.Text="Provider result recorded with your evidence.";}
}
